PythonMiddleTechnical
Что такое генераторы в Python и чем они отличаются от обычных функций?
Генератор — функция с yield, которая выполняется лениво и сохраняет состояние между вызовами. В отличие от обычной функции, не создаёт коллекцию в памяти — идеален для обработки больших данных потоком.
Что такое генераторы
Генератор — это функция, содержащая ключевое слово yield. При вызове она не выполняется немедленно, а возвращает объект-генератор. Код выполняется лениво — только при запросе следующего значения через next() или в цикле for.
Отличие от обычной функции
- Обычная функция: выполняется полностью, возвращает одно значение через
return, состояние не сохраняется. - Генератор: приостанавливается на
yield, сохраняет локальное состояние (переменные, позицию выполнения), возобновляется при следующем вызовеnext().
def regular_func(n: int) -> list[int]:
"""Создаёт весь список в памяти сразу."""
result = []
for i in range(n):
result.append(i * i)
return result
def squares_generator(n: int):
"""Вычисляет значения лениво, по одному."""
for i in range(n):
yield i * i
# Разница в памяти при n=10_000_000
import sys
regular = regular_func(10_000_000)
print(sys.getsizeof(regular)) # ~80 MB
gen = squares_generator(10_000_000)
print(sys.getsizeof(gen)) # ~112 байт
Жизненный цикл генератора
def countdown(n: int):
print(f"Старт с {n}")
while n > 0:
yield n
n -= 1
print("Завершён")
gen = countdown(3) # Код не выполняется
print(next(gen)) # Старт с 3 → 3
print(next(gen)) # 2
print(next(gen)) # 1
print(next(gen)) # Завершён → StopIteration
Практические применения
import csv
from pathlib import Path
from typing import Iterator
def read_large_csv(path: Path) -> Iterator[dict]:
"""Читает CSV построчно без загрузки всего файла в память."""
with open(path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
yield row
def filter_active(rows: Iterator[dict]) -> Iterator[dict]:
for row in rows:
if row.get("status") == "active":
yield row
def transform(rows: Iterator[dict]) -> Iterator[dict]:
for row in rows:
row["email"] = row["email"].lower().strip()
yield row
# Конвейер обработки — вся цепочка работает лениво
pipeline = transform(filter_active(read_large_csv(Path("users.csv"))))
for user in pipeline:
process_user(user) # В памяти только одна строка
send() и двунаправленные генераторы
def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
acc = accumulator()
next(acc) # Инициализация — доходим до первого yield
acc.send(10) # 10
acc.send(20) # 30
acc.send(5) # 35
Подводные камни
- Генератор одноразовый: после исчерпания повторный проход по нему ничего не даст — нужно создавать заново.
- Исключение внутри генератора: если
yieldнаходится вtry, код вfinallyвыполнится при закрытии генератора черезgen.close()или сборщиком мусора. - return в генераторе: значение
return Xпередаётся как атрибутStopIteration.value, а не возвращается напрямую. - Утечка ресурсов: если генератор открыл файл или соединение, а его не довели до конца — вызовите
gen.close()явно или используйтеcontextlib.closing. - Сериализация: объект-генератор нельзя сериализовать в pickle — при использовании в multiprocessing нужно передавать данные, а не генераторы.
- Ошибка в yield from: при использовании
yield from subgenисключение из подгенератора пробрасывается наверх — не всегда очевидно, где оно возникло.
Common mistakes
- Описывать generators только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом Python и реальной эксплуатацией.
What the interviewer is testing
- Объясняет generators через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.