Не уверен, почему StopIteration не обрабатывается "yield from"

Глядя на следующий пример кода из книги "Свободный Python", который объясняет функциональность "двунаправленного туннеля" yield fromУ меня следующий вопрос.

from collections import namedtuple

Result = namedtuple('Result', 'count average')


# the subgenerator
def averager():  # <1>
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield  # <2>
        if term is None:  # <3>
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)  # <4>


# the delegating generator
def grouper(results, key):  # <5>
    while True:  # <6>
        results[key] = yield from averager()  # <7>


# the client code, a.k.a. the caller
def main(data):  # <8>
    results = {}
    for key, values in data.items():
        group = grouper(results, key)  # <9>
        next(group)  # <10>
        for value in values:
            group.send(value)  # <11>
        group.send(None)  # important! <12>
        print("wrapped up grouper")
    print(results)


data = {
    'girls;kg':
        [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    'girls;m':
        [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
    'boys;kg':
        [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    'boys;m':
        [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}

Почему я получаю StopIteration исключение, когда я заменяю генератор делегирования выше со следующим генератором делегирования?

def grouper(results, key):
    results[key] = yield from averager()

Из того, что я узнал до сих пор, в теории кажется, что удаление while True все должно быть в порядке. group.send(None) вызовет averager() сопрограмма к break и вернуть Result(...), который будет передан делегирующему генератору. И тогда делегирующий генератор завершит, назначив Result(...) в results[key],

Но вместо этого происходит следующее.

Traceback (mostrecent call last):
  File "coroaverager3.py", line 111, in <module>
    main(data)
  File "coroaverager3.py", line 83, in main
    group.send(None)  # important! <12>
StopIteration

Любое понимание?

1 ответ

Решение

Вы правы, что делегирующий генератор назначает results[key], но это не заканчивается этим. Его выполнение продолжается, поскольку приостановить его негде. Конечно, он сразу падает с конца, вызывая send(None) поднимать StopIteration (так как нет значения (из yield) для того, чтобы вернуться).

while True: это своего рода глупый способ добавить другое место для приостановки; один дополнительный yield после yield from было бы немного более очевидным. (Он может предоставить значение, отличное от значения, предоставленного внутренним генератором, если клиент не всегда знает, когда завершится выполнение.)

Другие вопросы по тегам