Сообщить об ошибке.

Выражение yield from в Python

Передача генератором управления другому генератору

Для простых итераторов iterable выражение yield from iterable - это просто сокращенная форма for item in iterable: yield item:

def gen_list1(iterable):
    for i in list(iterable):
        yield i

# эквивалентно

def gen_list2(iterable):
    yield from list(iterable)
    
>>> list(gen_list1('python'))
# ['p', 'y', 't', 'h', 'o', 'n']
>>> list(gen_list2('home'))
# ['h', 'o', 'm', 'e']

Что если iterable, то же будет генератором?

Если не в даваться в подробности, то генераторы можно представить как простой удобный интерфейс к итераторам с дополнительными методами. В этом случае - выражение yield from <expression> позволяет генератору делегировать часть своих операций другому генератору <expression>. Это позволяет разделить код, содержащий yield основного генератора и выложить его в другой генератор. Кроме того, под-генератору разрешено возвращать значения, которые становятся доступным для делегирующего (основного) генератора.

В отличие от обычного цикла, yield from <expression> позволяет другому генератору <expression> получать отправленные и брошенные значения непосредственно из вызывающей области и возвращать окончательное значение внешнему генератору.

Хотя выражение yield from предназначено главным образом для передачи части вычислений под-генератору, оно фактически позволяет передавать управление произвольным генераторам.

Как это работает:

Когда выполнение натыкается на выражение yield from <expression>, все приходящие через generator.send()/generator.__next__() запросы проксируются в <expression>, а значения yield соответственно передаются сразу наружу. Так происходит до тех пор, пока <expression> не кончится, т. е. не выбросит исключение StopIteration, после этого выполнение внешнего генератора продолжается.

Все это делает генераторы очень похожими на сопрограммы. Они вызываются несколько раз, у них более одной точки входа, и их выполнение может быть приостановлено. Единственное отличие состоит в том, что функции генератора не может контролировать, где выполнение должно продолжаться после ее завершения и управление всегда передается вызывающей стороне генератора.

Примеры использования:

def accumulate():
    tally = 0
    while 1:
        next = yield
        if next is None:
            return tally
        tally += next

def gather_tallies(tallies):
    while 1:
        tally = yield from accumulate()
        tallies.append(tally)

>>> tallies = []
>>> acc = gather_tallies(tallies)
# Смотрим, что `accumulate()` готов принять значения
>>> next(acc) 
>>> for i in range(4):
...     acc.send(i)
...
# Завершить первый подсчет
>>> acc.send(None)
>>> for i in range(5):
...     acc.send(i)
...
# Завершите второй подсчет
>>> acc.send(None)
>>> tallies
# [6, 10]

Основная принцип введения выражения yield from <expression> заключается в том, чтобы даже генераторы, разработанные для использования с методами generator.send() и generator.throw(), можно было разделить на несколько под-генераторов так же легко, как одну большую функцию можно разделить на несколько под-функций.