Функция-генератор возвращает значение через return. Какой результат даст код и где окажется число 42?
def load():
yield 1
return 42
iterator = load()
print(next(iterator))
try:
next(iterator)
except StopIteration as error:
print(error.value)
Код напечатает 1, затем 42. Значение после return в генераторе не становится элементом последовательности: оно записывается в атрибут value исключения StopIteration, которым генератор завершает работу.
Генераторы появились как способ строить ленивые последовательности без предварительного хранения всех элементов в памяти. Они сохраняют своё состояние между вызовами next() и при завершении должны сообщить итератору, что дальнейших элементов нет.
Для обычного цикла значение завершения обычно не требуется: цикл обрабатывает элементы до StopIteration и скрывает это исключение. Однако при композиции генераторов нужно иногда передать итог вычисления от внутреннего генератора внешнему коду.
yield и return в генераторе имеют разные роли. Если перепутать их, итоговое значение можно принять за очередной элемент либо потерять при обходе через for.
Это важно для конвейеров обработки данных: генератор может выдавать промежуточные записи, а после завершения сообщать агрегированный результат — например, количество обработанных элементов или контрольную сумму.
При первом вызове next(iterator) выполнение доходит до yield 1, поэтому возвращается элемент 1, а состояние функции сохраняется. Следующий вызов продолжает выполнение после yield, встречает return 42 и завершает генератор.
Внутри это выражается исключением StopIteration(42). Поэтому обработчик получает объект исключения, а error.value равен 42.
return 42 не выдаёт 42 потребителю как элемент. В частности, такой код напечатает только 1:
Цикл перехватывает StopIteration и не предоставляет вызывающему коду его атрибут value. Чтобы передать результат вложенного генератора наружу, применяют yield from:
Здесь yield from делегирует выдачу элемента 1, перехватывает значение завершения 42 и присваивает его переменной result. Самостоятельно выбрасывать StopIteration из тела генератора не следует: начиная с Python 3.7 это приводит к RuntimeError из-за правила PEP 479. Для завершения генератора используется именно return.
Главный компромисс таков: yield подходит для потока элементов, а return — для одного итогового результата. Если итог должен обрабатываться обычным потребителем последовательности, его лучше явно выдать через yield или вернуть отдельной структурой, потому что for значение StopIteration.value скрывает.
Генератор читает большой файл, выдаёт корректные строки и после окончания должен сообщить число пропущенных повреждённых записей. Вариант с дополнительным элементом вроде ('summary', skipped) смешивает данные и служебную информацию и заставляет каждого потребителя проверять тип записи.
Вариант с внешним счётчиком проще для некоторых случаев, но создаёт разделяемое изменяемое состояние и плохо подходит для вложенных генераторов. return skipped вместе с yield from разделяет поток данных и итоговый результат, поэтому выбран именно этот подход.
Результат: потребитель получает только строки как элементы, а управляющий код получает число пропусков после завершения делегированного генератора. При обычном for итог не виден, поэтому его извлекают через yield from либо вручную обрабатывают StopIteration.
Дополнительный вопрос: Что произойдёт, если вызвать next() после того, как генератор уже завершился?
Ответ: Каждый такой вызов снова выбросит StopIteration, но значение завершения обычно доступно только в объекте исключения конкретного вызова. Генератор не запускается заново и не возвращается к началу; для нового прохода нужно повторно вызвать функцию-генератор.
Дополнительный вопрос: Почему list(load()) не содержит число 42?
Ответ: list() получает элементы через протокол итератора и прекращает сбор при StopIteration. Атрибут value этого исключения не считается элементом последовательности и не добавляется в список, поэтому результатом будет [1].
Дополнительный вопрос: Как безопасно получить итог вложенного генератора, не перехватывая вручную StopIteration?
Ответ: Нужно использовать yield from внутри другого генератора. Выражение result = yield from child() передаёт наружу все элементы child(), а его значение return присваивает переменной result. Это корректно отделяет элементы потока от итогового результата и избегает ручной обработки внутреннего протокола завершения.