В генераторе вспомогательная функция поднимает StopIteration. Какое исключение увидит потребитель?
В Python 3.7 и новее потребитель обычно увидит RuntimeError, а не нормальное завершение генератора. Это происходит, когда StopIteration покидает тело генератора; Python преобразует его, чтобы случайная ошибка во вспомогательной функции не выглядела как корректный конец последовательности.
До изменения, закреплённого в PEP 479, случайный StopIteration внутри генератора мог незаметно завершить его. Такая ошибка была особенно трудноуловимой: потребитель видел обычное окончание итерации и не понимал, что генератор завершился из-за сбоя во внутренней логике.
В Python 3.7 это поведение стало стандартным: StopIteration, выходящий из тела генератора, преобразуется в RuntimeError. При этом StopIteration, которым сам итератор сообщает протоколу о завершении, продолжает использоваться нормально.
Представим генератор, который получает следующий элемент через вспомогательную функцию. Если функция ошибочно поднимает StopIteration, генератор может преждевременно прекратить обработку данных.
Без преобразования потребитель принял бы такую ситуацию за штатный конец входного потока. В результате часть данных могла бы молча потеряться, а причина сбоя осталась бы незаметной.
Когда StopIteration возникает внутри тела генератора и выходит наружу через границу генератора, Python заменяет его на RuntimeError. Исходное исключение обычно сохраняется как причина через механизм цепочки исключений.
Первый вызов next() напечатает начало. Второй вызов завершится RuntimeError, потому что StopIteration из read_one() покинул тело генератора.
Это правило не означает, что StopIteration запрещён в итераторах вообще. Обычный объект-итератор должен поднимать его из своего метода __next__, когда элементы закончились. Также специальный механизм yield from корректно обрабатывает StopIteration от делегируемого итератора: его значение используется как результат делегирования.
Если генератор явно перехватит StopIteration внутри собственного тела, исключение не выйдет за границу и преобразование не произойдёт. Для обозначения нормального результата генератора следует использовать return, а не явный raise StopIteration.
В обработчике пакетного импорта генератор последовательно читает записи, а вспомогательная функция получает очередную запись из буфера. При пустом буфере она по ошибке поднимает StopIteration. Если позволить этому исключению выйти из генератора, импорт завершится с RuntimeError, что сразу укажет на дефект.
Можно было бы перехватывать StopIteration вокруг каждого вызова вспомогательной функции. Плюс такого подхода — явное управление пустым буфером; минус — риск смешать штатный конец входного итератора с ошибкой внутренней функции.
Предпочтительное решение — договориться, что вспомогательная функция возвращает специальный результат или поднимает отдельное прикладное исключение, например BufferEmptyError. Тогда StopIteration остаётся частью протокола итератора, а ошибки бизнес-логики не маскируются под завершение обхода.
Прекратится ли генератор нормально, если StopIteration возник внутри обработчика исключений?
Если исключение перехвачено внутри генератора и обработчик сам не выпускает StopIteration наружу, преобразования не будет. Генератор продолжит выполнение после обработчика или завершится другим способом, например через return.
Почему return в генераторе не вызывает тот же эффект, что raise StopIteration?
return — специальный штатный способ завершить генератор. Интерпретатор превращает возвращаемое значение в StopIteration.value, которое получает непосредственный потребитель или механизм yield from. Напротив, явный StopIteration, покинувший тело генератора, считается подозрительным завершением и преобразуется в RuntimeError.
Будет ли yield from считать ошибкой StopIteration от делегируемого итератора?
Нет, это предусмотренный случай. Делегирующий генератор использует StopIteration делегируемого итератора для определения его завершения, а значение StopIteration.value становится результатом выражения yield from. Защита PEP 479 относится к StopIteration, который возникает непосредственно в теле текущего генератора и пытается выйти из него.