Генераторы (Generators) в Python

Генераторы (Generators) в Python


Генераторы (Generators) в Python

Определение

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


Ключевое слово yield

Когда функция содержит yield, она автоматически становится генератором. При вызове next() выполнение функции приостанавливается на yield, а при следующем вызове возобновляется с того же места.

def simple_generator():
    yield 1
    yield 2
    yield 3

gen = simple_generator()
print(next(gen))  # 1
print(next(gen))  # 2

Генераторные выражения

Аналогичны списковым включениям, но используют круглые скобки () и возвращают элементы по одному.

# Генераторное выражение
gen_expr = (x**2 for x in range(5))
print(next(gen_expr))  # 0 (элементы генерируются на лету)

Сравнение с включениями (Comprehensions)

КритерийСписковое включениеГенератор
ПамятьХранит все элементы в памятиГенерирует элементы на лету
ИспользованиеДля небольших данныхДля больших/бесконечных данных
Пример[x**2 for x in range(10)](x**2 for x in range(10))

Методы генераторов

1. send(value)

Передает значение в генератор (становится результатом yield).

def responsive_gen():
    while True:
        received = yield
        print(f"Received: {received}")

gen = responsive_gen()
next(gen)          # Запускает генератор
gen.send("Hello")  # Received: Hello

2. throw(type)

Вызывает исключение внутри генератора.

def error_gen():
    try:
        yield 1
    except ValueError:
        yield "Error"

gen = error_gen()
next(gen)                # 1
print(gen.throw(ValueError))  # Error

3. close()

Останавливает генератор.

gen = simple_generator()
gen.close()
next(gen)  # StopIteration

Корутины (Coroutines)

Генераторы с send() могут использоваться как корутины — функции, обменивающиеся данными с вызывающим кодом. Это основа асинхронного программирования в Python (до появления async/await).

def coroutine():
    while True:
        x = yield
        print(f"Processing: {x}")

coro = coroutine()
next(coro)       # Инициализация
coro.send(10)    # Processing: 10

Копирование генераторов

Генераторы нельзя скопировать стандартными методами (deepcopy, copy, срезы), так как они хранят состояние выполнения.

Примеры ошибок:

import copy
gen = simple_generator()
# copy.copy(gen)    # Ошибка: генераторы не поддерживают копирование
# gen_slice = gen[1:]  # Ошибка: генераторы не поддерживают срезы

Решение:

  • Пересоздайте генератор.
  • Используйте itertools.tee() для разделения итератора на несколько (но это работает только для некоторых итераторов).
import itertools
gen = simple_generator()
gen1, gen2 = itertools.tee(gen, 2)
print(next(gen1))  # 1
print(next(gen2))  # 1 (но исходный gen будет истощен)

Заключение

  • Генераторы идеальны для работы с большими данными или бесконечными последовательностями.
  • Генераторные выражения экономят память, заменяя списковые включения.
  • Методы send(), throw(), close() расширяют возможности генераторов, превращая их в корутины.
  • Корутины позволяют реализовать кооперативную многозадачность.
  • Копирование генераторов невозможно из-за их состояния — используйте пересоздание или itertools.tee().

Совет: Для сложных сценариев (например, асинхронности) используйте современные конструкции asyncio и async/await, которые развивают идеи генераторов.