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-сценарий с ожидаемым поведением.
  • Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.

Sources

Related topics