Как работают генераторы в Python?

«Как работают генераторы в Python?» — вопрос из категории Python, который задают на 33% собеседований Data Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Генераторы в Python — это функции, которые используют ключевое слово yield для возврата итератора с ленивым (отложенным) вычислением. Их главная ценность — экономия памяти, так как они генерируют значения по одному по мере необходимости, а не хранят всю последовательность в памяти сразу.

Как это работает:

  1. При вызове функции-генератора она возвращает объект-генератор, но код не выполняется.
  2. При первой итерации (например, next() или в цикле for) выполнение идет до первого yield. Значение после yield возвращается, а состояние функции (локальные переменные, точка выполнения) «замораживается».
  3. При следующей итерации выполнение возобновляется сразу после yield и продолжается до следующего yield или конца функции.

Пример с состоянием:

def fibonacci_generator(limit):
    """Генерирует числа Фибоначчи до limit."""
    a, b = 0, 1
    while a < limit:
        yield a          # Возвращаем текущее значение и «засыпаем»
        a, b = b, a + b  # Это состояние сохраняется между вызовами

# Использование
fib_gen = fibonacci_generator(50)
print(next(fib_gen))  # 0
print(next(fib_gen))  # 1
print(next(fib_gen))  # 1
print(next(fib_gen))  # 2

# Или в цикле (остальные числа)
for num in fib_gen:
    print(num)  # 3, 5, 8, 13, 21, 34

Ключевые преимущества и use-cases:

  • Обработка больших файлов: Чтение файла построчно без загрузки всего в память.
    def read_large_file(file_path):
        with open(file_path, 'r') as f:
            for line in f:
                yield line.strip()
  • Работа с бесконечными последовательностями: Поток данных, сенсоры.
  • Конвейеры обработки данных (generator pipeline): Можно соединять генераторы, передавая данные по цепочке.

    def filter_even(numbers):
        for n in numbers:
            if n % 2 == 0:
                yield n
    
    def square(numbers):
        for n in numbers:
            yield n ** 2
    
    # Ленивый конвейер: квадраты четных чисел
    pipeline = square(filter_even(range(100)))
    print(list(pipeline)[:5])  # [0, 4, 16, 36, 64]
  • Выражения-генераторы: Краткий синтаксис (x**2 for x in range(10)).