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

Изучая Python или работая с ним определенное количество времени, неизбежно возникает вопрос «Являются ли list comprehensions (они же списковые включения) идиоматическими и когда их стоит использовать?»

Источником вопроса может послужить Zen of Python и его постулаты «Явное лучше, чем неявное», «Простое лучше, чем сложное» и «Читабельность имеет значение». Исходя из этого, списковые включения могут расцениваться как менее идиоматические, чем их более явные альтернативы, такие как вложенные циклы. В то же время, большое количество статей в интернете пестрит метками «плохой код» применительно к программам, написанным многословнее – без использования однострочников, лямбда-выражений, списковых включений и т.д. Все это выливается в неверный посыл, особенно для начинающих разработчиков.

При этом большинство опытных программистов сходится во мнении, что list comprehensions – в целом приятный и удобный механизм. PEP 202 говорит следующее:

Списковые включения обеспечивают более краткий способ создания списков в ситуациях когда предполагается использовать map(), filter или вложенные циклы.

В одном из своих выступлений, посвященных дизайну языка, которое называлось Python Regrets, Гвидо Ван Россум рассуждал по поводу лямбда-выражений и элементов функционального программирования:

  • map(), filter()
  • использование Python функций здесь является медленным
  • списковые включения решают ту же задачу лучше
  • reduce1
  • никто не пользуется этой функцией, немногие ее понимают
  • цикл – более явный и простой способ, и обычно быстрее

Также стоит отметить его статью «От списковых включений к генераторам»2:

Утверждалось, что настоящая проблема заключалась в том, что лямбда-нотация слишком многословна, и что более краткая нотация для анонимных функций сделает map() более привлекательной для использования. Я не согласен – я нахожу списковые включения более читабельными, чем функциональную нотацию, особенно по мере того, как увеличивается сложность отображаемого выражения. В дополнение, списковые включения выполняются намного быстрее, чем решения с map() и lambda. Это происходит потому, что вызывая лямбда-функцию, создается новый стек-фрейм, в то время как выражения в списковых включениях выполняются без создания стек-фрейма.

Т.е. map() быстрее списковых включений только тогда, когда вы не используете lambda.

В случаях когда не предъявляется никаких требований к производительности3, я стараюсь придерживаться баланса между использованием списковых включений, циклов и функционального стиля. Вложенные списковые включения или списковые включения с логикой внутри не всегда легко читать. Также, я уверен, что разработчик должен понимать смысл map(), reduce(), filter() независимо от того использует он их или нет.


  1. The fate of reduce() in Python 3000 ↩︎

  2. В Python 3 map() является итератором, что помогает при ленивых вычислениях. В некоторых языках списковые включения также являются ленивыми, но не в Python. Python поддерживает ленивые списковые включения в виде генераторов. ↩︎

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