Определите вывод программы и объясните, в какой точке генератора обрабатывается исключение, переданное через throw().
def worker():
try:
yield "готов"
except ValueError as error:
yield f"обработано: {error}"
yield "завершение"
generator = worker()
print(next(generator))
print(generator.throw(ValueError("ошибка")))
print(next(generator))
Программа выведет:
Метод throw() возбуждает указанное исключение внутри генератора в точке, где тот был приостановлен на yield. Исключение перехватывается блоком except, а throw() возвращает значение следующего достигнутого yield.
Обычный генератор первоначально рассматривался как источник последовательности, который приостанавливается на yield. Для взаимодействия с генератором во время паузы Python дополнил протокол методами send(), throw() и close().
Это позволило использовать генераторы не только для ленивого обхода данных, но и как простые корутины: внешний код может передать генератору значение, сообщить об ошибке или инициировать завершение без общего изменяемого состояния.
Генератор может ожидать данные от внешнего источника, например ответа сервиса или следующего элемента конвейера. Если во время ожидания произошла ошибка, вызывающему коду нужно доставить её именно в контекст генератора, чтобы тот мог выполнить локальную очистку, повторить операцию или выдать запасной результат.
Если вместо throw() просто выбросить исключение снаружи, оно не попадёт в try внутри генератора. Неправильное понимание этого механизма может привести к пропуску очистки ресурсов или к ожиданию значения там, где фактически должна быть обработана ошибка.
После next(generator) функция выполняется до первого yield и возвращает строку "готов". Генератор сохраняет состояние выполнения: место продолжения находится сразу после этого yield.
Вызов generator.throw(ValueError("ошибка")) не выбрасывает исключение в месте вызова обычным способом. Python возобновляет генератор и возбуждает ValueError в сохранённой точке приостановки. Поэтому управление передаётся в соответствующий except.
Обработчик выполняет первый yield со строкой "обработано: ошибка". Именно это значение возвращается из throw(), поэтому второй print() выводит его. Следующий вызов next() продолжает выполнение после этого yield и получает строку "завершение".
Если исключение не будет перехвачено внутри генератора, вызов throw() завершится этим исключением для вызывающего кода, а генератор больше нельзя будет продолжить. Если обработчик завершит функцию через return, генератор также завершится с соответствующим значением в объекте StopIteration.
Минимальная схема взаимодействия выглядит так:
Здесь throw() возвращает "повтор", потому что обработчик исключения дошёл до yield. Если бы обработчик не содержал yield и завершил генератор, throw() не вернул бы обычное значение, а сообщил бы завершение через StopIteration либо передал необработанное исключение.
В асинхронном или потоковом конвейере генератор ожидает очередное событие. При тайм-ауте внешний управляющий код должен либо дать генератору возможность выполнить восстановление, либо немедленно прекратить обработку.
Можно использовать общий флаг ошибки. Такой подход прост, но требует проверок в теле генератора, создаёт связанность между компонентами и может оставить ресурс открытым, если проверка пропущена.
Можно напрямую выбросить исключение в вызывающем коде. Это хорошо отделяет управление ошибкой, но внутренний try генератора не увидит исключение, поэтому локальная стратегия восстановления не сработает.
Использование throw() подходит, когда генератор является владельцем локального состояния и должен сам решить, как обработать ошибку. Внешний код вызывает throw(TimeoutError(...)), генератор закрывает или переоткрывает ресурс в своём try/except/finally, после чего продолжает работу или завершается. Такой вариант выбран, если протокол взаимодействия явно документирован; для сложной конкурентной логики обычно предпочтительнее полноценные средства asyncio или специализированная очередь сообщений.
Что именно возвращает throw()?
throw() возвращает значение, полученное при первом следующем yield, до которого дошёл генератор после обработки исключения. Если обработчик не делает yield, обычного результата у throw() нет: генератор либо завершится, либо наружу выйдет другое исключение.
Можно ли передать через throw() класс исключения вместо экземпляра?
Да. Например, generator.throw(ValueError) допустим: Python создаст экземпляр исключения без аргументов. Можно передать экземпляр, а также форму с типом, значением и трассировкой, но на практике наиболее ясно использовать класс без параметров или готовый экземпляр с нужным сообщением.
Что произойдёт, если генератор не находится на паузе?
throw() предназначен для генератора, который уже создан и приостановлен на yield. Если генератор ещё не запускался или уже завершён, ожидаемого продолжения внутри тела не произойдёт; исключение будет обработано правилами состояния генератора и обычно передано вызывающему коду. Поэтому перед throw() нужно гарантировать корректный жизненный цикл: генератор должен быть активным и приостановленным.