List comprehension — это способ собрать новый список в одну строку прямо там, где вы обычно писали бы цикл for с append. Вместо трёх строк (создать пустой список, пройтись циклом, добавлять элементы) получается одна: [x * x for x in range(10)]. Читается почти как фраза на английском: «возьми x в квадрате для каждого x из диапазона». Ниже разберу синтаксис по кусочкам, покажу вложенные и словарные версии, сравню скорость с циклом и map/filter и честно скажу, где эта конструкция начинает мешать, а не помогать.
Что такое list comprehension и зачем он нужен?
Это компактная запись создания списка на основе какого-то другого перебираемого объекта: другого списка, диапазона, строки, ключей словаря. Сравните два варианта, которые делают ровно одно и то же:
# обычный цикл
squares = []
for x in range(10):
squares.append(x * x)
# list comprehension
squares = [x * x for x in range(10)]
Результат идентичный: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]. Разница в том, что второй вариант короче, и когда вы читаете код через месяц, вам не нужно держать в голове «а что там накапливается в пустом списке». Всё видно сразу: что берём (x * x), откуда (range(10)).
Для аналитика это ежедневный инструмент. Достали список словарей из API, надо вытащить одно поле — comprehension. Отфильтровали заказы по статусу — comprehension. Это питоничный способ, то есть именно так пишут на Python, а не переносят привычку из других языков. На собеседовании junior-аналитика умение развернуть цикл в comprehension и обратно проверяют почти всегда.
Как читать синтаксис [x for x in ... if ...]?
Разбейте выражение на три смысловых блока и читайте их справа налево от for.
[ выражение for элемент in источник if условие ]
# что кладём откуда берём что оставляем
Сначала мысленно найдите for элемент in источник — это тот самый цикл. Потом посмотрите на условие if в конце — оно решает, попадёт элемент в результат или нет. И только потом на выражение слева — что именно положить в список.
nums = [4, 7, 2, 9, 1, 8]
big = [n for n in nums if n > 5]
# [7, 9, 8]
Читаем: «для каждого n из nums, если n больше 5, положи n». Условие if здесь работает как фильтр — элементы, которые ему не соответствуют, просто выкидываются. Теперь пример ближе к работе аналитика:
orders = [
{"id": 1, "sum": 1200, "status": "paid"},
{"id": 2, "sum": 300, "status": "canceled"},
{"id": 3, "sum": 5400, "status": "paid"},
]
paid_sums = [o["sum"] for o in orders if o["status"] == "paid"]
# [1200, 5400]
За одну строку я прошёлся по заказам, оставил только оплаченные и вытащил из них суммы. На обычном цикле это заняло бы четыре строки и один лишний пустой список.
Как добавить if и else внутрь comprehension?
Тут прячется главная ловушка новичков, из-за которой код перестаёт запускаться. Есть два разных if, и они стоят в разных местах.
Первый — фильтрующий if, он идёт после for и решает, брать элемент или нет. Второй — тернарный if ... else, он идёт до for и решает, какое значение положить. Их легко перепутать.
# фильтр: оставляем только крупные заказы
big = [o["sum"] for o in orders if o["sum"] > 1000]
# [1200, 5400]
# преобразование: помечаем каждый заказ, ничего не выкидываем
labels = ["крупный" if o["sum"] > 1000 else "мелкий" for o in orders]
# ['крупный', 'мелкий', 'крупный']
Правило, которое я держу в голове: если есть слово else, оно всегда стоит слева, перед for, потому что это часть выражения «что положить». Если else нет — это фильтр, и он справа. Смешивать их тоже можно (сначала тернарник слева, потом фильтр справа), но такой код уже тяжело читать, и я обычно разбиваю его на цикл.
Потренироваться в фильтрации и преобразованиях удобно на живых задачах в Python-тренажёре — там сразу видно результат, а разбор задач по темам есть в справочнике.
Что такое вложенные list comprehension?
Вложенность бывает двух видов, и их тоже путают. Первый — когда вы разворачиваете двумерную структуру в плоский список.
matrix = [[1, 2, 3], [4, 5, 6]]
flat = [x for row in matrix for x in row]
# [1, 2, 3, 4, 5, 6]
Порядок for здесь такой же, как во вложенном цикле: внешний слева, внутренний справа. Читается «для каждой строки row в matrix, для каждого x в этой строке». Помогает мнемоника: пишите for-части в том же порядке, в каком написали бы вложенные циклы сверху вниз.
Второй вид — comprehension внутри выражения, когда для каждого элемента вы строите свой маленький список:
pairs = [[i, i * i] for i in range(4)]
# [[0, 0], [1, 1], [2, 4], [3, 9]]
Мой личный порог: одна вложенность — ещё нормально, две — уже спорно, три — почти всегда лучше обычный цикл. Код, который надо перечитывать три раза, экономит строки, но крадёт время у того, кто будет его поддерживать (часто это вы сами).
Чем dict и set comprehension отличаются от list?
Тот же синтаксис работает и для словарей, и для множеств — меняются только скобки и, в случае словаря, пара «ключ-значение».
# dict comprehension — фигурные скобки и двоеточие
prices = {"хлеб": 45, "молоко": 89, "сыр": 560}
with_vat = {name: round(p * 1.2) for name, p in prices.items()}
# {'хлеб': 54, 'молоко': 107, 'сыр': 672}
# set comprehension — фигурные скобки без двоеточия
events = ["click", "view", "click", "scroll", "view"]
unique = {e for e in events}
# {'click', 'view', 'scroll'}
Словарное включение я использую постоянно, когда надо пересчитать значения по всем ключам: пересчитать цены с НДС, нормализовать проценты, привести названия к нижнему регистру. Множественное — когда нужны уникальные значения и порядок не важен, например собрать список уникальных типов событий из лога.
Одна деталь про словари: если в источнике встретятся одинаковые ключи, последний перезапишет предыдущий — молча, без ошибки. Это и плюс (быстрая дедупликация по ключу), и источник багов, если вы этого не ждали.
List comprehension быстрее обычного цикла?
Обычно да, но выигрыш небольшой, и гнаться за ним ради скорости не стоит. Причина техническая: в цикле for с list.append интерпретатор на каждой итерации заново ищет метод append у объекта списка, а comprehension выполняется на оптимизированном внутреннем механизме без этого поиска.
На практике разница видна только на больших объёмах — сотни тысяч и миллионы элементов, — и составляет она обычно проценты или десятки процентов, а не разы. Точная цифра зависит от версии Python, от того, что именно вы вычисляете, и от железа, поэтому доверять надо не ощущениям, а замеру timeit на своих данных.
Гораздо важнее другое: если внутри comprehension вы делаете что-то дорогое (обращение к базе, разбор строки, обращение к API), то скорость определит именно эта операция, а не форма записи цикла. Comprehension не ускорит медленную функцию внутри себя. А для действительно тяжёлых числовых расчётов в аналитике всё равно берут не чистый Python, а векторные операции в pandas и numpy — они работают на порядок быстрее любого цикла. Как раз такие задачи разбираем в разделе алгоритмических задач.
Что лучше — comprehension или map/filter?
Функции map и filter решают те же задачи, но выглядят иначе, и в современном Python comprehension почти всегда читаемее.
nums = [4, 7, 2, 9, 1, 8]
# map + filter
result = list(map(lambda x: x * x, filter(lambda x: x > 5, nums)))
# то же самое через comprehension
result = [x * x for x in nums if x > 5]
# [49, 81, 64]
Вторая строка читается слева направо как обычная фраза, а первую приходится распутывать изнутри наружу, ещё и оборачивать в list(), потому что map и filter возвращают ленивые объекты, а не готовый список. Поэтому в проектах на Python comprehension считается предпочтительным стилем.
Когда map всё же лучше: если у вас уже есть готовая именованная функция и никакого фильтра, запись map(str, ids) короче и чище, чем [str(i) for i in ids], и по скорости с готовой функцией (без lambda) map иногда даже чуть выигрывает. Но как только появляется условие или сложное выражение — берите comprehension.
Когда list comprehension вредит читаемости?
Есть несколько сигналов, что пора вернуться к обычному циклу. Первый — строка не помещается на экран без горизонтальной прокрутки. Второй — больше одного уровня вложенности или больше одного фильтра. Третий — вам нужно внутри итерации не только собрать значение, но и сделать побочное действие: залогировать, положить в две разные структуры, обработать исключение.
# так делать не надо — comprehension ради побочного эффекта
[print(o["id"]) for o in orders]
# обычный цикл честнее и понятнее
for o in orders:
print(o["id"])
Comprehension придуман, чтобы создать список. Если список вам не нужен, а нужно просто что-то поделать в цикле, обычный for выражает намерение точнее и не создаёт мусорный список из None, который потом никто не использует. Хорошее правило: если вслух объяснить строку одним предложением не получается — разворачивайте в цикл. Экономия символов не стоит потерянного на расшифровку времени.
Как отвечать про list comprehension на собесе junior-аналитика?
Этот вопрос почти гарантированно всплывёт, и хороший ответ состоит из трёх частей. Сначала одно предложение по сути: «это компактный способ создать список на основе итерации, часто с фильтром или преобразованием». Потом покажите, что понимаете границы: «читаемо для простого преобразования и одного условия, но при вложенности или побочных эффектах я беру обычный цикл». И третье — упомяните, что есть dict и set comprehension с тем же синтаксисом, а альтернатива в виде map/filter существует, но обычно менее читаема.
Частая подколка от интервьюера: попросят развернуть готовый цикл в comprehension прямо на месте или наоборот. Поэтому важно уметь делать это в обе стороны, не задумываясь. Ещё могут спросить про разницу между if как фильтром и if ... else как тернарником — это ровно та ловушка, которую мы разобрали выше, и на ней спотыкается половина кандидатов.
Прогнать такие вопросы в формате диалога можно в AI-интервьюере, а типовые сценарии рабочих задач с данными — в разборах кейсов. Если хотите закрепить именно основы Python перед собесом, соберите себе подборку задач в тренажёре и порешайте по 15 минут в день — этого хватает, чтобы конструкция ушла в мышечную память.
List comprehension — маленькая тема, но она хорошо показывает, думаете вы «по-питоновски» или переносите привычки из другого языка. Освойте фильтр, тернарник, dict/set-версии и чувство меры с вложенностью — и на junior-собесе этот блок вы закроете уверенно.
Если хотите системно прокачать Python и SQL к собеседованию, в Pro открыты все 402 Python-задачи, разборы кейсов и безлимитный AI-интервьюер — можно готовиться в своём темпе, без спешки.