学习Python很多书籍都或多或少介绍协程,但是很少有人会在开发中使用,一是理解不深,使用怕出错,二是有其他方式可以替代,知识代码多一点,便于理解啊,总之:不使用的理由千千种。
今天我就来和大家聊聊Python协程
协程是啥
协程在实际的应用
协程的定义
协程 是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序”。从技术的角度来说,“协程就是你可以暂停执行的函数”
与生成器类似,但yield
出现在表达式的右边,且产出一个表达式值作为返回值,如果yield
后没有表达式,则返回None
。协程可以从调用方接收数据,caller.send(params)。
1. 例子
>>> def simple(a):
print "start:a = ", (a)
b = yield a
print "received b= ",(b)
c = yield a + b
print "received :d ", (c)
>>> m1 = simple(2)
>>> m1
<generator object simple at 0x0000000003D830D8>
>>> next(m1) # 激活协程, 并产出a,然后暂停
start:a = 2
2
>>> m1.send(3)
received b= 3
5
>>> m1.send(1000) # b 接收数据,并产出 a+b 的结果,然后暂停
received :d 1000
Traceback (most recent call last):
File "<pyshell#11>", line 1, in <module>
m1.send(1000)
StopIteration
把yield
作为控制流程的方式来理解。
上图中 第一阶段 yield a
结束,第二阶段是给b
赋值开始
再看一个例子:
def averager():
total = 0
count = 0
avg = None
while True:
term = yield avg
total += term
count +=1
avg = total/count
ag = averager()
next(ag) # 运行到 yield avg 就停止,并返回avg值 None
ag.send(100) # item 接收到100,并运行,直到下一个yield 并停止
2. 与协程相关的方法
我们知道了怎么创建协程,但是当执行到最后,如果不处理就会和生成器一样抛出一个stopInteration
异常来终止该协程,从2.5版本开始添加了两个方法throw
和close
来终止协程
如果generator.thow(exception)的异常被捕获,则继续下一个yield,否则终止
3. 协程返回值
在定义体中 return value
from collections import namedTuple
Result = namedTuple("Result","count average")
def average():
total = 0
count = 0
avg = None
while True:
term = yield
if term is None:
break # 终止符
total +=term
count +=1
avg = total/count
return Result(count,avg)
当send(None)
就会break, 返回。其中Result 会作为异常的value属性返回
try:
m2 .send(None)
exception StopInteration as exc:
print exc.value
应用
# BEGIN YIELD_FROM_AVERAGER
from collections import namedtuple
Result = namedtuple('Result', 'count average')
# the subgenerator
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>
# the delegating generator
def grouper(results, key): # <5>
while True: # <6>
results[key] = yield from averager() #生成一个使用协程的生成器,在此处暂停,等 将一个Result 赋值给对应的key
# the client code, a.k.a. the caller
def main(data): # <8>
results = {}
for key, values in data.items():
group = grouper(results, key) # group是grouper函数生成的生成器对象
next(group) # 预激活协程
for value in values:
# 把各个 value 传给 grouper。传入的值最终到达 averager 函数中 term = yield 那一行; grouper 永远不知道传入的值是什么
group.send(value)
# 内层循环结束后, group 实例依旧在 yield from 表达式处暂停,因此, grouper函数定义体中为 results[key] 赋值的语句还没有执行
#把 None 传入 grouper,导致当前的 averager 实例终止,也让 grouper 继续运行,再创建一个 averager 实例,处理下一组值
group.send(None)
# print(results) # uncomment to debug
report(results)
# output report
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)
上述例子,如果子生成器不停止,委派生成器永远在yield from
处停止。
yield from iterable
本质上等于for item in iterable: yield item
的缩写
yield from
的意义
把迭代器当作生成器使用,相当于把子生成器的定义体内联在 yield from 表达式
中。此外,子生成器可以执行 return 语句,返回一个值,而返回的值会成为 yield
from 表达式的值
子生成器产出的值都直接传给委派生成器的调用方(即客户端代码将上述例子的averager 直接给了main中的group)。
使用
send()
方法发给委派生成器的值都直接传给子生成器。如果发送的值是
None
,那么会调用子生成器的__next__()
方法。如果发送的值不是None
,那么会
调用子生成器的send()
方法。如果调用的方法抛出StopIteration 异常
,那么委
派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。生成器退出时,生成器(或子生成器)中的
return expr
表达式会触发
StopIteration(expr)
异常抛出。yield from
表达式的值是子生成器终止时传给StopIteration
异常的第一个参
数。
** yield from
结构的另外两个特性与异常和终止有关。**
- 传入委派生成器的异常,除了
GeneratorExit
之外都传给子生成器的throw()
方
法。如果调用throw()
方法时抛出StopIteration
异常,委派生成器恢复运
行。StopIteration
之外的异常会向上冒泡,传给委派生成器。 - 如果把
GeneratorExit
异常传入委派生成器,或者在委派生成器上调用close()
方
法,那么在子生成器上调用close()
方法,如果它有的话。如果调用close()
方法
导致异常抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出
GeneratorExit
异常。