示例 16-13 是 averager 协程的不同版本,这一版会返回结果。为了说明如何返回值,每次激活协程时不会产出移动平均值。这么做是为了强调某些协程不会产出值,而是在最后返回一个值(通常是某种累计值)。
示例 16-13 中的 averager 协程返回的结果是一个 namedtuple,两个字段分别是项数(count)和平均值(average)。我本可以只返回平均值,但是返回一个元组可以获得累积数据的另一个重要信息——项数。
示例 16-13 coroaverager2.py:定义一个求平均值的协程,让它返回一个结果
from collections import namedtuple
Result = namedtuple('Result', 'count average')
def averager():
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) ➋
➊ 为了返回值,协程必须正常终止;因此,这一版 averager 中有个条件判断,以便退出累计循环。
➋ 返回一个 namedtuple,包含 count 和 average 两个字段。在 Python 3.3 之前,如果生成器返回值,解释器会报句法错误。
下面在控制台中说明如何使用新版 averager,如示例 16-14 所示。
示例 16-14 coroaverager2.py:说明
averager行为的 doctest
>>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(10) ➊
>>> coro_avg.send(30)
>>> coro_avg.send(6.5)
>>> coro_avg.send(None) ➋
Traceback (most recent call last):
...
StopIteration: Result(count=3, average=15.5)
❶ 这一版不产出值。
❷ 发送 None 会终止循环,导致协程结束,返回结果。一如既往,生成器对象会抛出 StopIteration 异常。异常对象的 value 属性保存着返回的值。
注意,return 表达式的值会偷偷传给调用方,赋值给 StopIteration 异常的一个属性。这样做有点不合常理,但是能保留生成器对象的常规行为——耗尽时抛出 StopIteration 异常。
示例 16-15 展示如何获取协程返回的值。
示例 16-15 捕获
StopIteration异常,获取averager返回的值
>>> coro_avg = averager() >>> next(coro_avg) >>> coro_avg.send(10) >>> coro_avg.send(30) >>> coro_avg.send(6.5) >>> try: ... coro_avg.send(None) ... except StopIteration as exc: ... result = exc.value ... >>> result Result(count=3, average=15.5)
获取协程的返回值虽然要绕个圈子,但这是 PEP 380 定义的方式,当我们意识到这一点之后就说得通了:yield from 结构会在内部自动捕获 StopIteration 异常。这种处理方式与 for 循环处理 StopIteration 异常的方式一样:循环机制使用用户易于理解的方式处理异常。对 yield from 结构来说,解释器不仅会捕获 StopIteration 异常,还会把 value 属性的值变成 yield from 表达式的值。可惜,我们无法在控制台中使用交互的方式测试这种行为,因为在函数外部使用 yield from(以及 yield)会导致句法出错。4
4iPython 有个扩展——ipython-yf(https://github.com/tecki/ipython-yf),安装这个扩展后可以在 iPython 控制台中直接执行 yield from。这个扩展用于测试异步代码,可以结合 asyncio 模块使用。这个扩展已经提交为 Python 3.5 的补丁,但是没有被接受。参见 Python 缺陷追踪系统中的 22412 号工单: Towards an asyncio-enabled command line(http://bugs.python.org/issue22412)。
下一节会举例说明如何使用 yield from 结构按照 PEP 380 定义的方式获取 averager 协程返回的值。下面讨论 yield from 结构。