Являются ли list comprehensions в Python идиоматическими?

Изучая 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() независимо от того использует он их или нет.


  1. The fate of reduce() in Python 3000 [return]
  2. В Python 3 map() является итератором, что помогает при ленивых вычислениях. В некоторых языках списковые включения также являются ленивыми, но не в Python. Python поддерживает ленивые списковые включения в виде генераторов.

    [return]
  3. Еще одной областью, где применение map() вместо списковых включений оправдано, является асинхронный и многопоточный код. Пример – модуль multiprocessing.

    [return]