Как понять «выход из» в сопрограмме python?

#python #coroutine #yield-from

Вопрос:

Код приходит в форме свободного Python 1-е изд.,

Я не могу понять строку while True: grouper , удалить эту строку вызывает StopIteration ошибку.

Но я нахожу новую версию grouper without while True: , которая работает. Зачем group.send(None) нужен еще один цикл while True: (или другой results[key] = yield from averager() )?

Насколько я понимаю group.send(None) , остановится yield from averager() и присвоит results[key] значение( Result(count, average) ). Это все.

 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>

# Another version works
#def grouper(results, key):
#    results[key] = yield from averager()
#    results[key] = yield from averager()

# 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(results)  # uncomment to debug
    report(results)


# output report
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(
              result.count, group, result.average, unit))


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],
}


if __name__ == '__main__':
    main(data)
 

Ответ №1:

Это заставляет меня вспомнить, насколько хорош аскинио и почему все должны им пользоваться…

То, что происходит, лучше всего объяснить, пройдя через работу итераторов. Это внутренний генератор, упрощенный:

 def averager():
    local_var
    while True:
        term = yield
        if term is None:
            break
        local_var = do_stuff(term)
    return local_var
 

Это делает две вещи. Во-первых, он получает некоторые данные yield (тьфу, объясняя, что выбор слов просто сбивает с толку) до тех пор, пока эти данные не None будут . Затем, когда это так None , он поднимает значение a StopIterationException со значением local_var . (Это то, что делает возврат от генератора).

Вот внешний генератор:

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

Что это делает, так это предоставляет выход внутреннего генератора вызывающему коду до тех пор , пока внутренний генератор не поднимется StopIterationException , что автоматически фиксируется ( yield from оператором) и назначается. Затем он готовится сделать то же самое снова.

Тогда у нас есть код вызова:

 def main(data):
    results = {}
    for key, values in data.items():
        group = grouper(results, key)
        next(group)
        for value in values:
            group.send(value)
        group.send(None)
 

Что это делает, так это:

  • он повторяет внешний генератор ровно один раз
  • это раскрывает производительность внутреннего генератора, и он использует это ( .send ) для связи с внутренним генератором.
  • он «завершает» внутренний генератор отправкой None , после чего заканчивается первый yield from оператор и присваивается переданное значение.
  • в этот момент внешний генератор готовится отправить другое значение
  • цикл продолжается, и генератор удаляется с помощью сборки мусора.

что это за while True: петля?

Рассмотрим этот код, который также работает для внешнего генератора:

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

Единственное, что важно, — это то, что генератор не должен быть исчерпан, чтобы он не передавал исключение по цепочке, говорящее: «Мне больше нечего повторять».

P.S. смущен? Я был. Я должен был проверить это, прошло некоторое время с тех пор, как я пытался использовать coros на основе генератора. Они запланированы для удаления—используйте asyncio, это намного приятнее.

Комментарии:

1. Спасибо за подробное объяснение, я наконец понял, что StopIteration без while True: исходит от истощенного внешнего генератора, поэтому yield для его подавления используется другой.