使用yield from

代码引自《流畅的Python》16.7 使用yield from 一节

问题

为什么书中的案例里最后没有按预期收集到数据?

原因

因为子生成将return语句写到了循环内,所以调用方每次send()值到子生成器都会返回Result对象到委派生成器grouper,导致结果覆盖。

而最后一次send(None)触发子生成器退出循环时,又相当于子生成器做了一次return None,将委派生成器中结果进行覆盖,所以最终收集到的数据每个键的值都是None

底层机制

子生成器每次执行return语句后都会被释放,此时拥有程序控制权的委托生成器会再次生成一个子生成器,并走到yield语句处,等待调用方发送数据

修改方法

将子生成器的return语句放到循环之外,那么知道调用方send(None)时,才会返回最终的Result对象,最终收集到的结果正常

from collections import namedtuple

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


# 子生成器
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield    # main函数中的客户代码发送的各个值绑定到这里的term变量上
        if term is None:    # 至关重要的终止条件。如果不这么做,使用yield from调用这个协程的生成器会永远阻塞
            break
        total += term
        count += 1
        average = total / count
        # return Result(count, average)     *更改处*
    return Result(count, average)   # 返回的result会成为grouper函数中yield from表达式的值


# 委派生成器
def grouper(results, key):
    while True:     # 这个循环每次迭代时会新建一个averager实例,每个实例都是作为协程使用的生成器对象
        """
        grouper发送的每个值都会经由yield from处理,通过管道传给averager实例。
        grouper会在yield from表达式处暂停,等待averager实例处理客户端发来的值。
        averager实例运行完毕后,返回的值绑定到results[key]上。
        while循环会不断创建averager实例,处理更多的值。
        """
        results[key] = yield from averager()


# 客户端代码,即调用方
def main(data):
    results = {}
    for key, values in data.items():
        """
        group是调用grouper函数得到的生成器对象。
        传给grouper函数的第一个参数results用于收集结果。
        第二个参数是某个键,group作为协程使用
        """
        group = grouper(results, key)
        next(group)     # 预激group协程
        for value in values:
            """
            把各个value传给grouper。传入的值最终到达averager函数中term = yield那一行;
            grouper永远不知道传入的值是什么
            """
            group.send(value)
        """
        把None传入grouper,导致当前的averager实例终止,
        也让grouper继续运行,再创建一个averager实例,处理下一组值
        """
        group.send(None)
    print(results)
    report(results)


# 输出报告
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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值