Генераторы в Python: понятное руководство по yield, iter/next и лучшим практикам
Запрос «генераторы в Python» стабильно популярен: тема затрагивает и основы языка, и производительность. Ниже — практическое руководство: как работает yield, чем генераторы отличаются от списков, как использовать iter/next, yield from, и какие готовые шаблоны помогают писать быстрый и аккуратный код.
Что такое генератор
Генератор — это объект, который возвращает значения по одному за раз и поддерживает протокол итерации. Чаще всего генераторы создают с помощью функции, в которой используется ключевое слово yield.
def squares(n):
for i in range(n):
yield i * i # вместо return нескольких значений
g = squares(5)
print(g) # <generator object squares at ...>
print(list(g)) # [0, 1, 4, 9, 16]
Важно: генератор одноразовый. После полного обхода он «исчерпан».
Протокол итерации: iter() и next()
Любой итератор поддерживает функции iter() и next(). Генератор — это итератор, поэтому вы можете управлять им вручную.
def count(start=0):
i = start
while True:
yield i
i += 1
it = count(10)
print(next(it)) # 10
print(next(it)) # 11
# ... при необходимости вызывайте next(it) еще
Когда значения заканчиваются, итератор должен поднять StopIteration. Бесконечные генераторы не поднимают его сами по себе — их нужно ограничивать.
Генераторы против списков: память и скорость
Списки хранят все элементы в памяти сразу, генераторы — нет. На больших данных это критично.
import sys
N = 10_000_000
lst = [i for i in range(N)] # список
gen = (i for i in range(N)) # генераторное выражение
print(sys.getsizeof(lst)) # память под весь список (только «голова», без элементов)
print(sys.getsizeof(gen)) # память под объект-генератор (очень мало)
# Итоговую разницу дают сами элементы. Список выделит память под них сразу, генератор — по мере итерации.
Когда выбирать генератор: потоковая обработка, ленивые пайплайны, неизвестный заранее объём данных. Списки — когда нужно случайно обращаться к элементам, сортировать или переиспользовать последовательность многократно.
Генераторные выражения
Короткая запись генератора очень похожа на списковое включение, но с круглыми скобками:
squares = (x*x for x in range(10))
print(sum(squares)) # 285
Помните: это не список, повторно пройтись нельзя без пересоздания.
yield from: делегирование генерации
yield from позволяет «проксировать» значения из другого генератора или итератора. Код становится короче и читаемее.
def gen_a():
yield from range(3) # 0, 1, 2
def gen_b():
yield -1
yield from gen_a()
yield 99
print(list(gen_b())) # [-1, 0, 1, 2, 99]
Методы генератора: send, throw, close
Генераторы — это не только «поставщики» значений. Через них можно передавать данные обратно в функцию.
def accumulator():
total = 0
while True:
x = yield total # принимает значение через send()
if x is None:
continue
total += x
g = accumulator()
print(next(g)) # старт: вернет 0
print(g.send(10)) # 10
print(g.send(5)) # 15
g.close() # корректное завершение
Методы полезны для кооперативных корутин, но начинающим достаточно знать, что они существуют.
Практические шаблоны
1) Порционная обработка (chunked)
from itertools import islice
def chunked(iterable, size):
it = iter(iterable)
while True:
chunk = list(islice(it, size))
if not chunk:
break
yield chunk
for part in chunked(range(1, 11), 3):
print(part) # [1,2,3] [4,5,6] [7,8,9] [10]
2) Скользящее окно (sliding window)
from collections import deque
def sliding_window(iterable, size):
it = iter(iterable)
d = deque(islice(it, size), maxlen=size)
if len(d) < size:
return
yield tuple(d)
for x in it:
d.append(x)
yield tuple(d)
for w in sliding_window([1,2,3,4,5], 3):
print(w) # (1,2,3) (2,3,4) (3,4,5)
3) Бесконечные последовательности с ограничением
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
from itertools import islice
print(list(islice(fibonacci(), 10))) # первые 10 чисел Фибоначчи
4) Конвейеры обработки (pipeline)
def read_numbers(n):
for i in range(n):
yield i
def filter_even(iterable):
for x in iterable:
if x % 2 == 0:
yield x
def square(iterable):
for x in iterable:
yield x * x
pipeline = square(filter_even(read_numbers(10)))
print(list(pipeline)) # [0, 4, 16, 36, 64]
Частые ошибки и советы
- Генератор одноразовый: если нужна повторная итерация — пересоздавайте его или материализуйте в список при малом объёме данных.
- Не превращайте генераторы в список без необходимости — теряется выгода по памяти.
- next(it) без значения по умолчанию поднимет StopIteration — используйте next(it, default), если это ожидаемо.
- Отлаживайте генераторы через небольшие тестовые входные данные и принты внутри тела функции.
Когда использовать генераторы
- Большие или бесконечные последовательности.
- Потоковые пайплайны и ленивые вычисления.
- Когда важны «низкая память» и «ранний результат».
Хотите закрепить тему и прокачать остальные основы Python на практике? Посмотрите пошаговый курс «Python с нуля до Гуру» с упором на реальные задачи — отличное дополнение к этой статье.
Итоги
Генераторы в Python — простой способ писать быстрый и экономный по памяти код. Освоив yield, iter/next и несколько шаблонов (chunked, sliding window, pipeline), вы сможете уверенно строить производительные решения, не усложняя архитектуру.
-
Создано 26.12.2025 17:45:43
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.