Изучая Python или работая с ним определенное количество времени, неизбежно возникает вопрос «Являются ли list comprehensions (они же списковые включения) идиоматическими и когда их стоит использовать?»
Источником вопроса может послужить Zen of Python и его постулаты «Явное лучше, чем неявное», «Простое лучше, чем сложное» и «Читабельность имеет значение». Исходя из этого, списковые включения могут расцениваться как менее идиоматические, чем их более явные альтернативы, такие как вложенные циклы. В то же время, большое количество статей в интернете пестрит метками «плохой код» применительно к программам, написанным многословнее – без использования однострочников, лямбда-выражений, списковых включений и т.д. Все это выливается в неверный посыл, особенно для начинающих разработчиков.
При этом большинство опытных программистов сходится во мнении, что list comprehensions – в целом приятный и удобный механизм. PEP 202 говорит следующее:
Списковые включения обеспечивают более краткий способ создания списков в ситуациях когда предполагается использовать
map()
,filter
или вложенные циклы.
В одном из своих выступлений, посвященных дизайну языка, которое называлось Python Regrets, Гвидо Ван Россум рассуждал по поводу лямбда-выражений и элементов функционального программирования:
map()
,filter()
- использование Python функций здесь является медленным
- списковые включения решают ту же задачу лучше
reduce
1- никто не пользуется этой функцией, немногие ее понимают
- цикл – более явный и простой способ, и обычно быстрее
Также стоит отметить его статью «От списковых включений к генераторам»2:
Утверждалось, что настоящая проблема заключалась в том, что лямбда-нотация слишком многословна, и что более краткая нотация для анонимных функций сделает
map()
более привлекательной для использования. Я не согласен – я нахожу списковые включения более читабельными, чем функциональную нотацию, особенно по мере того, как увеличивается сложность отображаемого выражения. В дополнение, списковые включения выполняются намного быстрее, чем решения сmap()
иlambda
. Это происходит потому, что вызывая лямбда-функцию, создается новый стек-фрейм, в то время как выражения в списковых включениях выполняются без создания стек-фрейма.
Т.е. map()
быстрее списковых включений только тогда, когда вы не используете lambda
.
В случаях когда не предъявляется никаких требований к производительности3, я стараюсь
придерживаться баланса между использованием списковых включений, циклов и функционального стиля.
Вложенные списковые включения или списковые включения с логикой внутри не всегда легко читать.
Также, я уверен, что разработчик должен понимать смысл map()
, reduce()
, filter()
независимо
от того использует он их или нет.
-
В Python 3
map()
является итератором, что помогает при ленивых вычислениях. В некоторых языках списковые включения также являются ленивыми, но не в Python. Python поддерживает ленивые списковые включения в виде генераторов. ↩︎ -
Еще одной областью, где применение
map()
вместо списковых включений оправдано, является асинхронный и многопоточный код. Пример – модуль multiprocessing. ↩︎