一、yield协程
2001年,Python2.2引入了yield关键字实现生成器函数。而2006年,Python2.5又为生成器增加了额外的方法和功能,最重要的当属.send()方法。
与.__next__()方法一样,.send()方法致使生成器前进到下一个yield语句。不过,.send()方法还允许客户把数据发给生成器,.send()方法会把发送的值传给生成器中对应的yield表达的值。也就是说.send()方法允许客户代码和生成器之间双向交换数据。而__next__()方法只允许客户从生成器获取数据。
所以引出一个新的概念:通过.send()双向交换数据的生成器就是协程。而通过.__next__()单向获取数据的就是生成器。
协程是一个过程,这个过程通过.send()方法,实现与调用方相互协作。
我们来看《流畅的Python》上的一个例子:
def simple_coro2(a):
print('-> Started: a =',a)
b = yield a
print('-> Received: b=',b)
c = yield a + b
print('-> Received: c=',c)
my_coro2 = simple_coro2(14)
如果我们在控制台运行,会得到以下结果:
next(my_coro2)
-> Started: a = 14
Out[7]: 14
在上面的程序中,yield a大概相当于return a的意思,如果我们把b=yield a改成b=yield,simple_coro2函数就会返回一个None。我们再执行:
my_coro2.send(28)
-> Received: b= 28
Out[8]: 42
此时才会将28的值赋给变量b,返回a+b即42。再执行:
my_coro2.send(99)
-> Received: c= 99
Traceback (most recent call last):File "<ipython-input-9-977093b924ab>", line 1, in <module>
my_coro2.send(99)StopIteration
此时将99赋给变量c,打印消息,然后协程终止,抛出StopIteration异常。这个流程并不容易理解,请看图:
程度经过了三个阶段,前面两次都是到yield为止,左边的赋值是第二次调用才执行的。
从上面的程序可以看出,
1,协程通过.next()启动,.send()发送数据。
2,生成器与协程都包含yield,但是协程的yield关键字出现在表达式的右边。
3,协程和普通函数调用不同之处在于,它是一个交互式的过程,有来有回,多次调用,且变量能保持最新的状态。
二、yield from协程
Python3.3才引入yield from结构。yield from要比yield复杂,因为它的作用要比yield多得多。我们总结下yield from结构的基本特点:
1,替代嵌套for循环,调用iter(),获取迭代器(前一篇博客已讲)。
2,调用协程时,会自动预激活协程(相当于执行了一次next()方法)。
3,yield协程一般需要调用多次.send(),获取多个结果(通过yield返回值)。而yield from只需要获取最后一次执行的结果(协程执行完毕后,返回StopIteration.value)。
4,第3步中,yield from会自动捕获StopIteration异常。子生成器中止后,将return value赋给StopIteration.value,然后抛出StopIteration异常。然后当前yield from表达式会替换成子生成器中返回的StopIteration.value。而yield需要手动处理异常,并手动返回值。
来一个简单的例子,统计学生的平均成绩:
from collections import namedtuple
Student = namedtuple('Student','name average_score')
data = {'张三':[70,80,90],
'李四':[75,80,88],
'王五':[80,87,90]}
def averager():
total = 0.0
count = 0
average_score = None
while True:
score = yield
if score is None:
break
total +=score
count +=1
average_score = total/count
return average_score
def grouper(results,name):
while True: #重点1
results[name] = yield from averager()
def main(data):
results = {}
for name,scores in data.items():
group = grouper(results,name)
next(group)
for score in scores:
group.send(score)
group.send(None)#重点2
print(results)
if __name__ == '__main__':
main(data)
可以看出,凡是使用yield from结构,总共至少应该包含三部分:
1,委派生成器。即包含yield from表达式的部分,如上面的grouper方法;
2,子生成器。负责具体生成数据,如上面的averager方法;
3,调用方。负责调用委派生成器,如上面的main(data)方法。
这个程序除了前面讲到4点、3部分以外,还要强调上面代码后面注释:
重点2:为什么要发送一个None?
将None传入grouper,是为了终止averager实例,并将averager返回值赋给results[name]。如果不传这个None,委派生成器grouper会一直停留在yield from averager()处,不会给results[name]赋值。
重点1:这里为什么要加while True?
如果我们不加while True,那么当group.send(None)发送给averager()后,通过break中止程序averager(),程序会给results[name]赋值,然后因为后面没有yield了,所以grouper()会抛出StopIteration异常。其实也可以不加while True,而像下面这样加一个yield即可:
def grouper(results,name):
results[name] = yield from averager()
yield
我们上面说过,yield from可以捕获异常,但是grouper()本身也是一个生成器,它的StopIteration异常并没有处理。这个yield的作用就是让grouper不终止,从而不抛出StopIteration异常。当然也可以这样:
def main(data):
results = {}
for name,scores in data.items():
group = grouper(results,name)
next(group)
for score in scores:
group.send(score)
try:
group.send(None)
except StopIteration:
pass
print(results)
也就是在main方法中,手动捕获异常,防止程序出错。