В чём подвох декоратора, который пытается поймать исключение генераторной функции вокруг её вызова?
Такой декоратор обычно не поймает исключение, возникшее во время итерации генератора. Вызов генераторной функции лишь создаёт объект-генератор, а тело функции начинает выполняться позже — при next(), send() или другой операции итерации.
Чтобы перехватывать исключения из тела генератора, обработку нужно поместить вокруг yield from внутри обёртки-генератора либо явно оборачивать сам процесс итерации.
Генераторы появились как механизм ленивого получения последовательности: значения выдаются по одному, без предварительного построения всей коллекции в памяти. Это полезно для больших наборов данных, потоков и потенциально бесконечных последовательностей.
Декораторы решают другую задачу — позволяют добавлять к вызываемым объектам сквозное поведение, например журналирование или обработку ошибок. При сочетании этих механизмов важно учитывать, что вызов генераторной функции и выполнение её тела происходят в разные моменты.
Если обёртка выполняет исходную функцию внутри try и просто возвращает результат, она защищает только момент создания объекта-генератора. Исключение, возникающее при последующем извлечении значения, произойдёт уже после завершения этого try.
Неверное решение создаёт ложное ощущение, что декоратор централизованно обрабатывает ошибки генератора. В рабочем коде это может привести к необработанным ошибкам при чтении файла, сетевого потока или результата конвейера обработки данных.
При вызове генераторной функции Python не исполняет её тело. Возвращается специальный объект, реализующий протокол итератора. Поэтому конструкция вида try: return функция() ловит только исключения, возникшие при подготовке самого объекта, но не ошибки, возникающие при его обходе.
Обёртка должна сама стать генератором и передавать значения через yield from. Тогда выполнение исходного генератора происходит внутри try, и исключение во время итерации попадает в соответствующий обработчик.
В этом примере source() возвращает генератор, а ValueError возникает только при втором next(g). Для обработки итерации обёртка должна использовать yield from внутри try.
Важно не путать ленивую обработку с преобразованием результата в список. Вызов list(fn(...)) действительно позволит обернуть всю итерацию в try, но уничтожит ленивость, увеличит потребление памяти и может быть непригоден для бесконечных потоков.
Сервисный декоратор должен журналировать ошибки при чтении записей из большого файла. Вариант с возвратом исходного генератора прост, но не видит исключения, возникающие во время чтения. Вариант с преобразованием в список видит ошибки, однако загружает все записи в память и задерживает выдачу первого результата.
Выбранное решение — обёртка-генератор с yield from и обработчиком вокруг него. Она сохраняет ленивость, передаёт значения по мере готовности и позволяет централизованно обработать ошибки итерации. Если потребителю важны операции send(), throw() и корректное закрытие генератора, yield from предпочтительнее ручного цикла с одним только for.
Сработает ли finally вокруг return исходная_функция() при завершении итерации?
Нет. finally выполнится сразу после получения объекта-генератора и возврата из обёртки, а не после исчерпания этого объекта. Чтобы finally охватывал фактическое выполнение генератора, обёртка должна сама поддерживать итерацию, например через yield from.
Поймает ли такой декоратор исключение, переданное генератору через throw()?
Обёртка с yield from может увидеть это исключение, если оно не обработано внутри исходного генератора и выходит наружу. Сам yield from корректно делегирует операции send(), throw() и close() подчинённому генератору, тогда как простое возвращение объекта-генератора вообще не участвует в этих операциях.
Почему обработчик except ValueError не должен перехватывать GeneratorExit без необходимости?
При закрытии генератора Python передаёт ему GeneratorExit. Это сигнал о завершении итерации, а не обычная прикладная ошибка. Его обычно не подавляют: генератор должен освободить ресурсы в finally и завершиться; чрезмерно широкая обработка исключений может нарушить корректное закрытие и скрыть реальные ошибки.