Генераторные выражения в Python
Генераторные выражения в Python: синтаксис, ленивые вычисления, экономия памяти, условия и сравнение со списковыми включениями.
Генераторное выражение — это компактный способ создать объект-генератор. Синтаксически похоже на списковое включение, но использует круглые скобки
()вместо квадратных[]и вычисляет элементы лениво — только по запросу.
Синтаксис
# Списковое включение — вычисляет ВСЁ сразу, хранит в памяти
squares_list = [n ** 2 for n in range(5)]
# Генераторное выражение — вычисляет по одному, только когда просят
squares_gen = (n ** 2 for n in range(5))
print(squares_list) # [0, 1, 4, 9, 16]
print(squares_gen) # <generator object ...>
print(list(squares_gen)) # [0, 1, 4, 9, 16]
Вывод:
[0, 1, 4, 9, 16] <generator object <genexpr> at 0x...> [0, 1, 4, 9, 16]
Перебор элементов
evens = (n for n in range(10) if n % 2 == 0)
for e in evens:
print(e, end=" ")
Вывод:
0 2 4 6 8
Ленивые вычисления и экономия памяти
Главное преимущество: генераторное выражение не хранит все элементы в памяти — оно вычисляет следующий элемент только тогда, когда его запрашивают.
import sys
n = 1_000_000
list_comp = [x ** 2 for x in range(n)]
gen_expr = (x ** 2 for x in range(n))
print(f"Список: {sys.getsizeof(list_comp):,} байт")
print(f"Генератор: {sys.getsizeof(gen_expr):,} байт")
Вывод (приблизительно):
Список: 8,448,728 байт Генератор: 104 байт
Генератор занимает ~100 байт независимо от числа элементов — он не хранит коллекцию, только логику вычисления следующего элемента.
Генераторное выражение vs генераторная функция
Одну и ту же логику можно записать двумя способами:
# Генераторная функция (с yield)
def squares_fn(n):
for i in range(n):
yield i ** 2
# Генераторное выражение
squares_ge = (i ** 2 for i in range(5))
# Результат одинаковый
print(list(squares_fn(5))) # [0, 1, 4, 9, 16]
print(list(squares_ge)) # [0, 1, 4, 9, 16]
Вывод:
[0, 1, 4, 9, 16] [0, 1, 4, 9, 16]
Генераторное выражение — это синтаксический сахар для простых генераторных функций без состояния.
С условием и вложенным циклом
# Только нечётные квадраты
odd_squares = (n ** 2 for n in range(10) if n % 2 != 0)
print(list(odd_squares))
Вывод:
[1, 9, 25, 49, 81]
# Декартово произведение (вложенный цикл)
pairs = ((x, y) for x in range(1, 3) for y in range(1, 3))
print(list(pairs))
Вывод:
[(1, 1), (1, 2), (2, 1), (2, 2)]
Передача в функцию без лишних скобок
Когда генераторное выражение — единственный аргумент функции, внешние скобки можно опустить:
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
total = sum(n ** 2 for n in numbers)
maxi = max(n * 2 for n in numbers)
joined = ", ".join(str(n) for n in numbers)
print(total)
print(maxi)
print(joined)
Вывод:
179 18 3, 1, 4, 1, 5, 9, 2, 6
Генератор исчерпывается
gen = (n for n in range(3))
print(list(gen)) # [0, 1, 2]
print(list(gen)) # [] — уже исчерпан!
# Создаём новый
gen = (n for n in range(3))
print(list(gen)) # [0, 1, 2]
Вывод:
[0, 1, 2] [] [0, 1, 2]
Сравнение: список, генераторное выражение, генераторная функция
| Критерий | Списковое включение | Генераторное выражение | Генераторная функция |
| Синтаксис | [...] | (...) | def + yield |
| Возвращает | список | объект-генератор | объект-генератор |
| Память | всё сразу | по одному элементу | по одному элементу |
| Повторный перебор | да | нет (исчерпывается) | нет (исчерпывается) |
| Сложная логика | нет | нет | да (несколько yield, try/except) |
Коротко
- Генераторное выражение —
(...)— создаёт объект-генератор с ленивыми вычислениями. - Экономит память: хранит логику, а не коллекцию — ~100 байт независимо от размера.
- Поддерживает фильтрацию (
if) и вложенные циклы — как списковые включения. - При передаче в функцию как единственный аргумент внешние скобки не нужны.
- Исчерпывается после однократного перебора — создавайте новый экземпляр для повтора.
Закрепите практикой
Задачи с автоматической проверкой — решайте прямо здесь, не уходя из учебника.