Для простых итераторов 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()
, можно было разделить на несколько под-генераторов так же легко, как одну большую функцию можно разделить на несколько под-функций.