Как ответить
У генераторов есть метод send(), throw() и close() — у обычных итераторов их нет. Разницу важно понимать на практике, особенно если работаешь с корутинами или управляешь потоком данных. Обычный итератор просто возвращает следующий элемент через __next__() или выбрасывает StopIteration. Генератор — это синтаксический сахар над итератором, но с дополнительными возможностями за счёт сохранения состояния и двусторонней связи.
send(value) — передаёт значение в генератор как результат последнего yield. Используется, например, для конфигурации поведения на лету. Допустим, у нас генератор, который копит строки, и через send мы указываем лимит:
def logger():
limit = 10
count = 0
while count < limit:
msg = yield
print(f"Log: {msg}")
count += 1
Первый вызов next() нужен, чтобы запустить генератор до первого yield. После этого gen.send("error") передаёт строку. Если бы это был обычный итератор, такой гибкости нет — он либо возвращает фиксированный элемент, либо перебирает коллекцию.
throw(type, value, traceback) — бросает исключение в точку yield. Генератор может перехватить его и продолжить или завершиться. Используется, когда нужно прервать или переключить режим работы внешней обработки. Пример: если генератор обрабатывает запросы, а мы хотим закрыть соединение, шлём GeneratorExit через close(). close() — это фактически throw(GeneratorExit), который гарантированно завершает генератор. Обычный итератор просто прекращает итерацию, но не обрабатывает внешние сигналы.
Ещё есть момент с атрибутами gi_frame и gi_running — они тоже специфичны для генераторов и помогают отследить состояние (выполняется ли сейчас, есть ли фрейм). В CPython это реализовано через структуру PyGenObject.
На практике я редко пользуюсь send и throw в продакшене — чаще их используют для кооперативной многозадачности (например, в asyncio до async/await). Но если нужно передать контекст внутрь генератора, то send — единственный чистый способ без внешнего состояния.