Объясните механизм: куда попадает значение, переданное генератору методом send?
Значение, переданное через send, становится результатом выражения yield, на котором генератор был приостановлен. Метод возобновляет выполнение генератора с этого места, поэтому присваивание после yield получает переданное значение.
В только что созданный генератор нельзя передать ненулевое значение: сначала его нужно запустить через next() или send(None).
Изначально генераторы в Python решали задачу ленивого получения последовательностей: они позволяли выдавать элементы по одному и не хранить весь результат в памяти. Такой подход уменьшает потребление памяти и позволяет обрабатывать потенциально большие или бесконечные потоки.
Позднее протокол генераторов расширили двусторонним обменом. Методы send, throw и close позволили не только получать значения от генератора, но и передавать ему данные или управлять его завершением.
Обычный вызов next() только возобновляет генератор и получает следующее значение. Для сценариев, где генератор должен реагировать на внешние данные, этого недостаточно: требуется передать информацию внутрь уже выполнявшегося вычисления.
Неверное понимание механизма приводит к ошибкам в инициализации. Ненулевое значение нельзя отправить в генератор до его первой остановки на yield, потому что ещё нет выражения yield, результатом которого оно могло бы стать.
При выполнении генератора выражение yield одновременно выдаёт значение наружу и приостанавливает выполнение. Когда вызывается send(value), генератор продолжает работу, а выражение yield внутри него вычисляется как value.
После первого next() генератор остановлен на yield total. Вызов send(3) подставляет число 3 вместо всего выражения yield total, поэтому переменная delta получает значение 3. Затем генератор продолжает работу до следующего yield.
send(None) эквивалентен обычному next() и используется для запуска или обычного возобновления генератора. Вызов send с ненулевым значением до первого yield приводит к TypeError.
Если переданное значение не соответствует ожидаемому формату, ошибка возникает уже внутри тела генератора. После завершения генератора последующие попытки возобновить его приводят к StopIteration.
Такой механизм полезен для специализированных генераторов, имитирующих сопрограммы, но он не заменяет современный asyncio: асинхронные функции и задачи предоставляют более явную модель планирования и ожидания операций ввода-вывода.
Сервис разбирает поток сообщений и должен принимать от внешнего кода команды изменения режима обработки. Генератор может выдавать очередной результат, приостанавливаться на yield и получать новую настройку через send без передачи состояния через глобальные переменные.
Вариант с глобальным состоянием проще написать, но он ухудшает тестируемость и может привести к конфликтам при повторном использовании обработчика. Передача состояния через параметры обычной функции требует вручную хранить промежуточное состояние между вызовами.
Вариант с send сохраняет состояние локально внутри генератора и делает точку обмена явной. Его недостаток — более сложный жизненный цикл: генератор нужно корректно запустить, обработать его завершение и не отправлять значения неподходящего типа.
Для небольшого последовательного конвейера выбран генератор с send, потому что состояние принадлежит одному экземпляру обработчика и не требует внешнего хранилища. Для конкурентной обработки и сетевого ввода-вывода предпочтительнее использовать asyncio, поскольку там управление задачами и ожиданием выражено средствами асинхронного протокола.
send(None) и next()?Функционально при возобновлении генератора они эквивалентны: оба продолжают выполнение до следующего yield и получают выданное значение. Разница проявляется в намерении: next() запрашивает следующий результат, а send(None) явно использует двусторонний интерфейс, не передавая содержательного значения.
send после завершения генератора?Генератор не запустится заново. Вызов приведёт к StopIteration, как и повторный вызов next() после исчерпания генератора. Значение, возвращённое через return, доступно в исключении, возникшем при завершившем генератор вызове; последующие вызовы уже не повторяют это возвращаемое значение как новый результат.
Да, для этого предназначен метод throw. Он возобновляет генератор так, будто исключение возникло в точке приостановленного yield. Генератор может перехватить его через try/except, продолжить работу или завершиться; если исключение не обработано, оно выйдет наружу вызывающему коду. Это отличается от send, который передаёт обычное значение как результат выражения yield.