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(). Генератор не хранит все элементы в памяти — экономия при обработке больших данных.