协程中委派生成器如何处理子生成器的StopIteration并获取异常附带的value呢?

本人最近在学习Python中的协程知识。遇到一处委派生成器利用while True处理子生成器的StopIteration并获取异常附带的值的代码例子。

代码实例问题描述:
在下面的实例中,我们需要利用委派生成器的知识将某班级的男女生身高和体重分别计算平均值并输出。第一段代码是可以算出正确结果的。

from collections import namedtuple

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

def subaverager():#子生成器
    total = 0.0
    number = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        number += 1
        average=total/number
    return Result(average,number)

def averager(results,key):#委派生成器
    while True:
        results[key]=yield from subaverager()

def main(grouper):
    results={}
    for key,group in grouper.items():
        term = averager(results,key)
        next(term)
        for value in group:
            term.send(value)
        term.send(None)
        print(results)#调试使用
    result(results)
    
def result(results):
    for key,value in results.items():
        gender,unit=key.split(';')
        print('{} {} averaging {:.2f} {}.'.format(
            value.number,gender,value.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)

正确输出如下:

{'girls;kg': Result(average=42.040000000000006, number=10)}
{'girls;kg': Result(average=42.040000000000006, number=10), 'girls;m': Result(average=1.4279999999999997, number=10)}
{'girls;kg': Result(average=42.040000000000006, number=10), 'girls;m': Result(average=1.4279999999999997, number=10), 'boys;kg': Result(average=40.422222222222224, number=9)}
{'girls;kg': Result(average=42.040000000000006, number=10), 'girls;m': Result(average=1.4279999999999997, number=10), 'boys;kg': Result(average=40.422222222222224, number=9), 'boys;m': Result(average=1.3888888888888888, number=9)}
10 girls averaging 42.04 kg.
10 girls averaging 1.43 m.
9 boys averaging 40.42 kg.
9 boys averaging 1.39 m.

在第一轮平均值的计算中,当主函数传给子生成器None使其终止时,子生成器会把StopIteration以及第一轮计算结果的值返回给委派生成器(就是averager())。我自己写上述代码时没有像正确代码一样,在委派生成器中加入while True。我写的averager()如下:

def averager(results,key):
    results[key]=yield from subaverager()#自己写委派生成器没有加while True

如果不添加While True,解释器报错StopIteration,主函数的print(results)也不会输出任何字符串。相反,如果添加while True,代码能够顺利运行。

所以我的理解是,委派生成器中的while True能够处理StopIteration并将异常的值赋给results[key],然后在下一个循环的yield from处阻塞。我不清楚这么理解对不对,恳请明白的前辈帮我指点迷津,感激不尽!!!

阅读 2.8k
2 个回答

不添加While True 我猜你是不是也没有执行term.send(None),执行send(None)可以拿到正确的结果

try:
    term.send(None)
except StopIteration:
    pass

没有send(None)的话subaverager的while True没有跳出,所以还没有运行到return,averager的yield from没有等到返回值,当然不会给results[key]赋值,所以results还是空dict

看这样你能理解么, 加上while True,term.send(None)后results[key]获得了数据,之后又运行yield from 等待下一个值,所以averager生成器没有抛出StopIteration,下一个值我特意减去了第一个人,你会发现前一个值被冲掉了,就是字典赋值的证据

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

def main(grouper):
    results={}
    for key,group in grouper.items():
        term = averager(results,key)
        next(term)
        for value in group:
            term.send(value)
        term.send(None)
        for value in group[1:]:
            term.send(value)
        term.send(None)
    result(results)

while True也可以换成yield,看看下面这个列子

def averager(results,key):
    results[key]=yield from subaverager()    #等待值
    yield #等待,防StopIteration

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

对于这个问题,打开链接,里面的总结部分解释的很详细了

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题