一. 让协程返回值
下面的例子,我们再次改版之前计算平均值的协程函数,这一版本的协程函数每次被激活时,不会自动产出平均值,而是在最后返回一个值。
from collections import namedtuple
 
Result = namedtuple('Result', 'count average')
 
 
def average():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)
为了返回值,协程必须正常终止,因此 average 中有个判断条件,以便退出累计循环。返回值为 namedtuple,包含 count 和 average 两个字段。
演示 1   发送 None 会终止循环,导致协程结束

注意:协程结束,协程对象会抛出 StopIteration 异常。return 表达式的值通过异常对象 StopIteration 传递给调用方。这样做有点不合常理,但是能保留生成器对象的常规行为,即在耗尽时抛出 StopIteration 异常。
演示 2  获取协程 return 的值

捕获 StopIteration 异常,并通过异常对象的 value 属性获取 average 返回的值。
下面我们介绍的 yield from 结构会在内部自动捕获 StopIteration 异常,这种处理方式与 for 循环处理 StopIteration 异常的方式一样:循环机制使用用户易于理解的方式处理异常。
对 yield from 结构来说,解释器不仅会捕获 StopIteration 异常,还会把 value 属性的值变为 yield from 表达式的值。
二. 使用 yield from
首先要明确,yield from 是全新的语法结构,其功能要比 yield 丰富。
演示 1  yield from 可用于简化 for 循环中的 yield 表达式

演示 2 使用 yield from 链接可迭代对象

yield from x 表达式对 x 对象所做的第一件事就是调用 iter(x) ,从中获取迭代器。因此 x 可以是任何可迭代的对象。
yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。
在举例前,我们先来了解几个专门的术语:
- 委派生成器:包含 
yield from <iterable>表达式的生成器函数。 - 子生成器:从 
yield from表达式中<iterable>部分获取的生成器。 - 调用方:调用委派生成器的客户端代码。在适当的语境中,将使用“客户端”代指“调用方”,以便于委派生成器(也是调用方,因为它调用了子生成器)区分开。
 
委派生成器在 yield from 表达式处暂停时,调用方可以直接把数据发给子生成器,子生成器再把产出的值发给调用方。子生成器返回之后,解释器会抛出 StopIteration 异常,并把返回的值附加到异常对象上,此时委派生成器会恢复。

示例  使用 yield from 计算平均值,并输出统计报告
from collections import namedtuple
 
Result = namedtuple('Result', 'count, average')
 
 
# 子生成器
def average():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)
 
 
# 委派生成器
def grouper(results, key):
    while True:
        results[key] = yield from average()
        print(results[key])
 
 
# 客户端代码,即调用方
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)
    report(results)
 
 
# 输出报告
def report(results):
    print('-'*20, 'report', '-'*20)
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print(f'{result.count:2} {group:5} averaging {result.average:.2f}{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)
运行结果:外层 for 循环每次迭代会产生一个 grouper 实例,赋值给 group 变量,group 是委派生成器。
调用 next(group),预激委派生成器 group,此时进入 while 循环,调用子生成器 average 后,在 yield from 表达式处暂停。内层 for 循环调用 group.send(value),直接把值传给子生成器 average。同时,当前的 group 在 yield from 表达式处暂停。
内层循环结束后,group 实例依旧在 yield from 表达式处暂停。因此,grouper 函数定义体中为 results[key] 赋值的语句还没有执行。如果外层 for 循环的末尾没有 group.send(None) ,那么 average 子生成器永远不会终止,委派生成器 group 永远不会再次激活,因此永远不会为 results[key] 赋值。
外层 for 循环重新迭代时会新建一个 grouper 实例,然后绑定到 group 变量上。前一个 grouper 实例,以及它创建的尚未终止的 average子生成器实例,被垃圾回收机制回收。

注意:
- 
group.send(None)以及average循环中的条件判断是至关重要的终止条件。如果不这么做,使用yield from调用这个协程的生成器会永远阻塞。 - 返回的 
Result会成为grouper函数中yield from表达式的值。 - 
grouper是委派生成器; - 这个循环每次迭代时会新建一个 
average实例,每个实例都作为协程使用的生成器对象。 - 
grouper发送的每个值都会经由yield from处理,通过管道传给average实例。group会在yield from表达式处暂停,等待average实例处理客户端发来的值。average实例运行完毕后,返回的值绑定到results[key]上。while循环会不断创建average实例,处理更多的值。 - 把各个 
value传给grouper,传入的值最终到达averager函数中term = yield那一行;grouper永远不知道传入的值是什么。 - 把 
None传入grouper,导致当前的averager实例终止,也让grouper继续运行,再创建一个averager实例,处理下一组值。 
这个实验想表名的关键一点是,如果子生成器不终止,委派生成器会在 yield from 表达式处永远暂停。如果是这样,程序不会向前执行,因为 yield from 把控制权交给客户代码(即委派生成器的调用方)了。
yield from 的意义
- 子生成器产出的值都直接传给委派生成器的调用方。
 - 使用 
send方法发给委派生成器的值都直接传给子生成器。如果发送的值是None,那么会调用子生成器的__next__方法。如果发送的值不是None,那么会调用子生成器的send方法。如果调用的方法抛出StopIteration异常,那么委派生成器恢复运行。任何其它异常都会向上冒泡,传给委派生成器。 - 生成器退出时,生成器中的 
return expr表达式会触发StopIteration异常抛出。 - 
yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数。 
关于 yield 实现生成器的介绍至此已经完成,下一节我们将使用 asyncio 来实现协程。










