协程
定义:协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。(协程中必定含有一条yield语句)
协程与生成器类似,都是定义体内包含yield关键字的函数。不过,在协程中,yield通常出现在表达式的右边(例如,data = yield),可以产出值,也可以不产出。
- 生成器不可以返回值,如果生成器中给return语句提供值,会抛出SyntaxError异常;
- python新引入yield from 语句,可以把复杂的生成器重构成小型的嵌套生成器,省去了大量样板代码。
三个方法:
- . send() 方法,可以让调用方给协程发送数据,发送的数据会成为协程函数中 yield 表达式的值。
- .throw() 方法,可以让调用方抛出异常
- .close() 方法,可以让调用方终止协程
四个状态:
- ‘GEN_CREATED’ 等待开始执行
- ‘GEN_RUNNING’ 解释器正在执行
- ‘GEN_SUSPENDED’ 在yield表达式处暂停
- ‘GEN_CLOASED’ 执行结束
协程只能处于这四个状态中的一个,当前状态可以由 inspect.getgeneratorstate(…)函数获取
因为send() 方法的参数会成为暂停的yield表达式的值,所以,仅当协程处于暂停状态时才能调用send()方法
协程需要被预激,预激是通过next()函数进行
给协程添加预激装饰器 functools.wraps(),可以省去协程的预激过程。
yield from
在生成器gen中使用yield from subgen()时,subgen()会得到当前的控制权,把产出的值传给gen的调用方,即调用方可以直接跳过gen控制subgen。当subgen得到控制权时,gen会阻塞,同时等待subgen终止。
一个小例子:
def chain(*iters):
for iter in iters:
yield from iter
lst_1 = 'abc'
lst_2 = '987'
print(list(chain(lst_1, lst_2)))
运行结果:
['a', 'b', 'c', '9', '8', '7']
这个例子还可以改写为:
def chain():
yield from 'abc'
yield from '987'
输出结果是一样的。
yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样,二者可以直接发送和产生值,甚至可以直接传入异常。
一个复杂的例子,计算中学生的平均身高和体重:
from collections import namedtuple
Result = namedtuple('Result', 'count average')
# 子生成器
def averager(): # <1>
total = 0.0
count = 0
average = None
while True:
term = yield # <2>
if term is None: # <3>
break
total += term
count += 1
average = total/count
return Result(count, average) # <4>
# 委派生成器
def grouper(results, key): # <5>
while True: # <6>
results[key] = yield from averager() # <7>
# 客户端代码,即调用端
def main(data): # <8>
results = {}
for key, values in data.items():
group = grouper(results, key) # <9>
next(group) # <10>
for value in values:
group.send(value) # <11>
group.send(None) # important! <12>
# print(results) # uncomment to debug
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)
运行结果:
9 boys averaging 40.42kg
9 boys averaging 1.39m
10 girls averaging 42.04kg
10 girls averaging 1.43m
- 委派生成器grouper()只是起到一个传输数据的作用,没有进行任何的数据处理。
生成器中都有一个无限循环 while True: 这个无限循环表明,只要调用方不断把值发送给这个协程,它就会一直接收值,然后生成结果。该循环结束条件:
- 调用方在协程上显式调用 .close() 方法,
- 或者没有对协程的引用,而被垃圾回收程序回收时,这个协程才会终止。
终止协程的方法
generator.close()
该方法致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。
- 如果生成器处理了这个异常,生成器一定不能产生值,否则解释器会抛出RuntimeError异常。
- 如果生成器没有处理这个异常,或者抛出StopIteration异常,即生成器已经运行到最后,调用方也不会报错。