При делегировании одного генератора через yield from где окажется значение, возвращённое делегируемым генератором?
Значение, возвращённое делегируемым генератором через return, не выдаётся наружу как элемент. Оно становится результатом выражения yield from и может быть присвоено переменной во внешнем генераторе.
Механически это значение передаётся через StopIteration.value. Если внешний генератор не использует результат выражения yield from, возвращённое значение теряется.
До появления yield from композицию генераторов приходилось реализовывать вручную: перебирать вложенный генератор, пересылать ему значения через send, обрабатывать исключения и корректно закрывать его.
Конструкция yield from, появившаяся в Python 3.3, стандартизировала это делегирование. Она решает не только задачу передачи элементов, но и корректно проксирует взаимодействие с вложенным генератором, включая его завершение и возвращаемое значение.
Внешний генератор может использовать другой генератор как часть своего алгоритма. Если просто получить вложенный генератор обычным вызовом, его элементы сами по себе не попадут во внешний поток; если вручную пересылать только элементы, можно потерять значение, переданное через send, исключения или операцию закрытия.
Особенно легко ошибиться с return: оно завершает генератор и не создаёт очередной элемент. Поэтому значение, возвращённое вложенным генератором, нельзя получить обычным обходом — его нужно извлечь из результата делегирования.
yield from временно передаёт управление делегируемому итерируемому объекту. Его элементы выдаются вызывающему коду через внешний генератор, а методы send, throw и close при соответствующих условиях направляются вложенному генератору.
Когда вложенный генератор выполняет return значение, он завершается исключением StopIteration. Переданное значение доступно в атрибуте value этого исключения, а выражение yield from преобразует его в обычный результат:
Результат — ['данные', 42]. Строка "данные" была выдана как элемент, а число 42 сначала стало результатом yield from, затем внешний генератор отдельно выдал его.
Если делегируется обычный итерируемый объект, например список, он не возвращает специального значения через StopIteration.value; результатом делегирования будет None. Значение имеет смысл именно для генераторов или других итераторов, которые завершаются с таким результатом.
Важно отличать yield from от обычного вызова генератора. Вызов только создаёт объект-генератор, тогда как yield from запускает делегирование обхода и протокола взаимодействия. Это удобнее и обычно надёжнее ручного цикла, но делает поток управления менее локальным: часть логики и возможных исключений находится во вложенном генераторе.
В ETL-пайплайне внешний генератор последовательно обрабатывает страницы API. Для каждой страницы используется вложенный генератор, который выдаёт записи, а после завершения возвращает число успешно обработанных элементов.
Рассматривались два варианта. Ручной цикл проще читать в самом примитивном случае, но он требует отдельно обрабатывать передачу значений, исключения и закрытие вложенного генератора. Сбор всех записей в список позволяет затем вернуть статистику обычным способом, но разрушает потоковую обработку и увеличивает потребление памяти.
Выбран yield from: записи обрабатываются потоково, а итоговое количество можно получить из результата делегирования и передать во внешний отчёт. Это сохраняет низкое потребление памяти и не требует вручную воспроизводить правила протокола генераторов.
Будет ли значение из return отдельным элементом генератора?
Нет. return завершает генератор, поэтому значение не появляется среди результатов next() или при обходе for. При использовании yield from оно становится результатом самого выражения делегирования.
Что произойдёт, если результат yield from не присвоить?
Делегированный генератор всё равно выдаст свои элементы, но его финальное значение не будет сохранено во внешнем генераторе. Оно будет обработано механизмом yield from, а затем фактически отброшено, если окружающая логика его не использует.
Работает ли yield from только с генераторами?
Нет. Он может делегировать любому итерируемому объекту, включая список, кортеж или пользовательский итератор. Однако полезное возвращаемое значение через yield from характерно для генератора, завершившегося через return; у обычных контейнеров результат завершения обхода обычно равен None.