В генераторе вызвали другой генератор обычным вызовом. Почему его элементы не становятся результатом внешнего обхода?
Обычный вызов генератора создаёт объект-генератор, но не запускает его обход. Если внешний генератор передаёт этот объект через yield, он выдаёт сам объект целиком; чтобы передать его элементы, используют делегирование через yield from.
Генераторы появились для ленивой выдачи последовательностей без хранения всех элементов в памяти. По мере усложнения программ стало необходимо составлять генераторы из нескольких подгенераторов и прозрачно передавать между ними значения, исключения и завершение.
Конструкция yield from, добавленная в Python 3.3, решает именно задачу делегирования. Она устраняет необходимость вручную писать цикл, который получает элементы подгенератора и повторно выдаёт их наружу.
Вызов функции-генератора возвращает объект, реализующий итерацию, но тело функции начинает выполняться только при получении очередного элемента. Поэтому передача результата такого вызова через обычный yield не означает автоматического обхода вложенного генератора.
Неверный выбор приводит к тому, что потребитель получает объект-генератор вместо ожидаемых данных. Это может вызвать ошибку типов, неожиданное поведение сериализации или незаметную потерю элементов, если полученный объект никто не начал отдельно обходить.
yield inner() означает: создать объект inner и выдать его как одно значение. Внешний генератор при этом не извлекает из него элементы.
yield from inner() означает: передать управление подгенератору и выдавать наружу каждое его значение. После завершения подгенератора внешний генератор продолжит выполнение со следующей инструкции.
yield from не просто сокращает цикл. Он также корректно передаёт подгенератору операции send, throw и close, а значение return подгенератора становится результатом выражения yield from и может быть сохранено внешним генератором.
Ограничение состоит в том, что делегировать можно итерируемому объекту, а не только генератору. При этом для обычного списка или кортежа отсутствует двустороннее взаимодействие генераторов, например обработка send.
В конвейере обработки файлов один генератор разбирает заголовок, а отдельные генераторы разбирают разные секции. Вариант с обычным yield возвращает потребителю вложенные объекты-генераторы, поэтому потребителю приходится знать внутреннюю структуру конвейера и запускать дополнительные циклы.
Можно вручную написать цикл for с повторным yield: это явно и подходит, если нужны дополнительные действия для каждого элемента, но требует самостоятельно учитывать завершение и передачу исключений. Можно собрать все результаты в список, однако это увеличивает потребление памяти и лишает конвейер ленивости.
Выбран yield from, потому что он сохраняет потоковую обработку, скрывает внутреннюю структуру этапов и корректно поддерживает протокол взаимодействия генераторов. В результате потребитель получает плоский поток элементов и не загружает весь файл в память.
yield?Нет. Вызов функции-генератора только создаёт объект-генератор, а выполнение начинается при next, итерации или другой операции извлечения элемента. Если этот объект выдан через обычный yield, он остаётся единственным значением внешнего генератора и может вообще не быть запущен.
yield from отличается от ручного цикла с for с точки зрения поведения?Оба варианта могут выдавать элементы подгенератора, но yield from дополнительно делегирует операции send, throw и close. Кроме того, оно позволяет получить значение, возвращённое подгенератором через return; простой цикл for сам по себе это значение не предоставляет.
yield from?При завершении подгенератора выражение return value вызывает StopIteration с атрибутом value. Конструкция yield from перехватывает это завершение и принимает указанное значение как результат самого выражения, поэтому внешний генератор может сохранить его в переменную и продолжить работу. Потребитель обычной итерации это значение напрямую не получает.