List comprehension в Python — генераторы списков
Коротко
List comprehension — компактный способ создать список в Python. Одна строка вместо цикла с append. Аналитики используют comprehensions постоянно: фильтрация колонок, создание словарей-маппингов, обработка данных. На собеседованиях спрашивают синтаксис, отличия от генераторных выражений и подводные камни.
Базовый синтаксис
[выражение for элемент in итерируемый_объект]Вместо классического цикла с append:
# Цикл
squares = []
for x in range(5):
squares.append(x ** 2)
# List comprehension — то же самое
squares = [x ** 2 for x in range(5)]
# [0, 1, 4, 9, 16]Читается как: «для каждого x из range(5) вычисли x ** 2 и положи в список».
Условие if — фильтрация
[выражение for элемент in итерируемый_объект if условие]if в конце отсекает элементы, не прошедшие условие:
numbers = [1, -3, 4, -7, 8, 2, -1]
# Только положительные
[x for x in numbers if x > 0]
# [1, 4, 8, 2]
# Строки длиннее 3 символов
words = ['id', 'name', 'age', 'email', 'city']
[w for w in words if len(w) > 3]
# ['name', 'email', 'city']if-else — тернарный оператор
Когда нужно не отфильтровать, а трансформировать с условием — тернарник ставится перед for:
[выражение1 if условие else выражение2 for элемент in итерируемый_объект]numbers = [1, -3, 4, -7, 8]
# Заменить отрицательные на 0
[x if x > 0 else 0 for x in numbers]
# [1, 0, 4, 0, 8]
# Категоризация
scores = [45, 78, 92, 33, 67]
['pass' if s >= 60 else 'fail' for s in scores]
# ['fail', 'pass', 'pass', 'fail', 'pass']Важно не путать: if после for — фильтр, if-else перед for — трансформация.
Вложенные циклы
Два цикла в одном comprehension — эквивалент вложенного for:
# Вложенный цикл
pairs = []
for x in [1, 2, 3]:
for y in ['a', 'b']:
pairs.append((x, y))
# Comprehension
pairs = [(x, y) for x in [1, 2, 3] for y in ['a', 'b']]
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'b')]Разворачивание вложенных списков (flatten):
nested = [[1, 2], [3, 4], [5, 6]]
[item for sublist in nested for item in sublist]
# [1, 2, 3, 4, 5, 6]Порядок чтения: внешний цикл — первый, вложенный — второй. Такой же, как в обычном for.
Dict comprehension
Словарь создаётся через фигурные скобки с двоеточием:
{ключ: значение for элемент in итерируемый_объект}# Словарь: имя -> длина
names = ['Анна', 'Борис', 'Вика']
{name: len(name) for name in names}
# {'Анна': 4, 'Борис': 5, 'Вика': 4}
# Инвертировать словарь
original = {'a': 1, 'b': 2, 'c': 3}
{v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# Фильтрация словаря
prices = {'яблоко': 80, 'манго': 350, 'банан': 60, 'авокадо': 290}
{k: v for k, v in prices.items() if v < 200}
# {'яблоко': 80, 'банан': 60}Set comprehension
Множество — фигурные скобки без двоеточия:
{выражение for элемент in итерируемый_объект}words = ['Python', 'python', 'PYTHON', 'Java', 'java']
{w.lower() for w in words}
# {'python', 'java'}Дубликаты убираются автоматически — это же множество.
Generator expression vs list comprehension
Круглые скобки вместо квадратных создают генераторное выражение — ленивый итератор, который не хранит все элементы в памяти:
# List comprehension — создаёт список целиком
sum([x ** 2 for x in range(1_000_000)])
# Generator expression — вычисляет по одному элементу
sum(x ** 2 for x in range(1_000_000))Генератор экономит память, но его можно итерировать только один раз. Используйте генератор, когда список нужен только для передачи в sum, max, min, any, all, join.
Когда НЕ использовать
Сложная логика. Если comprehension не помещается в одну строку или требует тройной вложенности — обычный цикл читабельнее.
# Плохо — нечитаемо
result = [f(x) for x in data if g(x) > 0 for y in h(x) if y != z]
# Лучше — обычный цикл
result = []
for x in data:
if g(x) > 0:
for y in h(x):
if y != z:
result.append(f(x))Побочные эффекты. Comprehension — для создания коллекции, а не для вызова функций ради побочных эффектов. [print(x) for x in items] — антипаттерн.
Огромные данные. Если результат не нужен весь сразу — используйте генераторное выражение или обычный цикл.
Производительность
List comprehension быстрее эквивалентного цикла с append примерно на 10–30%. Причина: comprehension оптимизирован на уровне байт-кода, append не вызывается как метод на каждой итерации.
# ~30% медленнее
result = []
for x in range(100_000):
result.append(x ** 2)
# Быстрее
result = [x ** 2 for x in range(100_000)]Но разница ощутима только на больших объёмах. Читаемость важнее.
Примеры для аналитика
# Отфильтровать числовые колонки
import pandas as pd
df = pd.DataFrame({'id': [1, 2], 'name': ['A', 'B'], 'score': [85, 92]})
numeric_cols = [col for col in df.columns if df[col].dtype in ['int64', 'float64']]
# ['id', 'score']
# Маппинг: код региона -> название
regions = [('MSK', 'Москва'), ('SPB', 'Петербург'), ('NSK', 'Новосибирск')]
region_map = {code: name for code, name in regions}
# Развернуть список списков (flatten)
user_tags = [['sql', 'python'], ['excel'], ['python', 'tableau']]
all_tags = [tag for tags in user_tags for tag in tags]
# ['sql', 'python', 'excel', 'python', 'tableau']
# Уникальные теги
unique_tags = {tag for tags in user_tags for tag in tags}
# {'sql', 'python', 'excel', 'tableau'}Вопросы с собеседований
-- Что такое list comprehension?
-- Компактный синтаксис для создания списка: [выражение for элемент in итерируемый_объект]. Эквивалент цикла for с append, но короче и быстрее.
-- Чем отличается [x for x in data if x > 0] от [x if x > 0 else 0 for x in data]?
-- Первое — фильтрация: элементы с x <= 0 отбрасываются. Второе — трансформация: все элементы остаются, но отрицательные заменяются на 0. if после for — фильтр, if-else перед for — тернарный оператор.
-- Чем list comprehension отличается от generator expression?
-- List comprehension [...] создаёт список в памяти целиком. Generator expression (...) — ленивый итератор, вычисляет элементы по одному. Генератор экономит память, но итерировать его можно только один раз.
-- Что выведет [x for x in range(10) if x % 2 == 0 if x % 3 == 0]?
-- [0, 6]. Два if — это AND: оба условия должны выполняться. Эквивалент: if x % 2 == 0 and x % 3 == 0.
-- Когда не стоит использовать list comprehension? -- Когда логика сложная (вложенные условия, несколько трансформаций), когда нужны побочные эффекты, и когда результат не нужен весь сразу (тогда лучше генератор).
Потренировать Python-вопросы на практике можно в тренажёре Карьерника. Подробнее про lambda-функции и разницу list vs tuple. А больше примеров вопросов — на отдельной странице.
Открыть тренажёр в Telegram — вопросы по Python, pandas, SQL и аналитике. Бесплатно.
FAQ
List comprehension быстрее обычного цикла?
Да, примерно на 10–30% для типичных задач. Comprehension оптимизирован на уровне байт-кода: нет вызова метода append на каждой итерации. Но разница заметна на больших объёмах, для маленьких списков непринципиально.
Можно ли использовать несколько if в одном comprehension?
Да. [x for x in data if cond1 if cond2] — это логическое AND. Эквивалент: if cond1 and cond2. Можно комбинировать с вложенными циклами.
Чем {x for x in data} отличается от {k: v for k, v in data}?
Первое — set comprehension, создаёт множество. Второе — dict comprehension, создаёт словарь. Фигурные скобки без двоеточия — множество, с двоеточием — словарь.
Когда использовать generator expression вместо list comprehension?
Когда список нужен только для передачи в агрегирующую функцию: sum, max, min, any, all, ''.join(). Генератор не хранит все элементы в памяти — экономия при обработке больших данных.