Traceback (most recent call last):
File "/Users/gs/coroutine.py", line 14, in <module>
my_coro.send(123)
TypeError: can't send non-None value to a just-started generator
def coroutinue(func):
'''
装饰器: 向前执行到第一个`yield`表达式,预激`func`
:param func: func name
:return: primer
'''
@wraps(func)
def primer(*args, **kwargs):
# 把装饰器生成器函数替换成这里的primer函数;调用primer函数时,返回预激后的生成器。
gen = func(*args, **kwargs)
# 调用被被装饰函数,获取生成器对象
next(gen) # 预激生成器
return gen # 返回生成器
return primer
@coroutinue
def averager():
# 使用协程求平均值
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
40.0
45.0
Traceback (most recent call last):
File "/Users/gs/coro_exception.py", line 37, in <module>
print(coro_avg.send('123'))
File "/Users/gs/coro_exception.py", line 30, in averager
total += term
TypeError: unsupported operand type(s) for +=: 'float' and 'str'
raise RuntimeError('This line should never run.') 永远不会执行,
因为只有未处理的异常才会终止循环,而一旦出现未处理的异常,协程会立即终止。
在使用generator.throw时候如果异常被处理,如下面的代码段中异常被传入DemoException,协程不会中止,直到遇到未处理的异常或是close(也是异常)才会终止。
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
# 返回一个namedtuple,包含count和average两个字段。在python3.3前,如果生成器返回值,会报错
return Result(count, average)
yield from
yield from 结果会在内部自动捕获StopIteration 异常。这种处理方式与 for 循环处理StopIteration异常的方式一样。
对于yield from 结构来说,解释器不仅会捕获StopIteration异常,还会把value属性的值变成yield from 表达式的值,yield和yield from不能再函数体外部使用。
yield from 是 Python3.3 后新加的语言结构。和其他语言的await关键字类似,它表示:*在生成器 gen 中使用 yield from subgen()时,subgen 会获得控制权,把产出的值传个gen的调用方,即调用方可以直接控制subgen。于此同时,gen会阻塞,等待subgen终止。
如下:
def gen():
yield from 'AB'
yield from range(1, 3) # 此处range()可以理解为subgen()
list(gen())
['A', 'B', '1', '2']
上述代码等同于:
def gen():
for c in 'AB':
yield c
for i in range(1, 3):
yield i
# Example of flattening a nested sequence using subgenerators
from collections import Iterable
def flatten(items, ignore_types=(str, bytes)):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
yield from flatten(x) # 这里递归调用,如果x是可迭代对象,继续分解
else:
yield x
items = [1, 2, [3, 4, [5, 6], 7], 8]
# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):
print(x)
items = ['Dave', 'Paula', ['Thomas', 'Lewis']]
for x in flatten(items):
print(x)
yield from基本概念:
委派生成器
包含yield from <iterable>表达式的生成器函数
子生成器
从yield from <iterable>部分获取的生成器。
调用方
调用委派生成器的客户端(调用方)代码
yield from应用实例:
#! -*- coding: utf-8 -*-
from collections import namedtuple
Result = namedtuple('Result', 'count average')
# 子生成器
# 这个例子和上边示例中的 averager 协程一样,只不过这里是作为子生成器使用
def averager():
total = 0.0
count = 0
average = None
while True:
# main 函数发送数据到这里
term = yield
if term is None: # 终止条件
break
total += term
count += 1
average = total/count
return Result(count, average) # 返回的Result 会成为grouper函数中yield from表达式的值
# 委派生成器
def grouper(results, key):
# 这个循环每次都会新建一个averager 实例,每个实例都是作为协程使用的生成器对象
while True:
# 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 = grouper(results, key)
next(group)
for value in values:
# 把各个value传给grouper 传入的值最终到达averager函数中;
# grouper并不知道传入的是什么,同时grouper实例在yield from处暂停
group.send(value)
# 把None传入groupper,传入的值最终到达averager函数中,导致当前实例终止。然后继续创建下一个实例。
# 如果没有group.send(None),那么averager子生成器永远不会终止,委派生成器也永远不会在此激活,也就不会为result[key]赋值
group.send(None)
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))
6 boys averaging 54.00kg
6 boys averaging 1.68m
6 girls averaging 44.00kg
6 girls averaging 1.58m
这段代码展示的是yield from 结构最简单的用法。委派生成器相当于管道,所以可以把任意数量的委派生成器连接在一起—一个委派生成器使用yield from 调用一个子生成器,而那个子生成器本身也是委派生成器,使用yield from调用另一个生成器。最终以一个只是用yield表达式的生成器(或者任意可迭代对象)结束。