在python2.5的时候,yield关键字可以在表达式中使用,而且生成器API中增加了send()方法,也就是从这个时候开始,生成器可以当作协程使用。
在python3.3的时候,PEP380引入了yield from语句,使用它可以将复杂的生成器重构称小型嵌套生成器;而且从这个版本开始,生成器可以返回值,在此之前,在生成器中加入return语句会抛出SyntaxError。
在python3.4的时候,引入了库asyncio标准库,直接内置了对异步IO的支持。
在python3.5的时候,引入了async/await语句,让协程的实现更加的方便。
本系列文章就以上面的发展史,说说协程进化。
yield/send时代
有了前面一片文章对协程的介绍,这里我就直接贴出测试代码!
import random
import time
def fibonacci(n):
index = 0
a,b = 0,1
while index < n:
sleep_cnt = yield b
print('let me think {} secs'.format(sleep_cnt))
time.sleep(sleep_cnt)
a,b = b,a+b
index += 1
def main():
n = 10
sfib = fibonacci(n)
fib_res = next(sfib)
while True:
print(fib_res)
try:
fib_res = sfib.send(random.uniform(0,0.5))
except StopIteration:
break
if __name__ == "__main__":
main()
这个程序实现了斐波拉契数列的计算,其中第一次调用next(sfib)这句时,相当于slib.send(None)去预激协程。后续的sfib.send(random.uniform(0, 0.5))则将一个随机的秒数发送给sfib,作为当前中断的yield表达式的返回值。
这里主要时说明我们可以从main程序中控制协程计算斐波拉契数列的时间,而协程返回给main程序计算结果。注意,send方法发送的数据时给了sleep_cnt变量。
yield from的用处
首先,yield from可以简化yield语句!
def gen1():
for c in "ABC":
yield c
def gen2():
yield from "ABC"
print(list(gen1()))
print(list(gen2()))
上面的代码输出效果是一样的,也就是说,yield只能返回一个元素;而yield from能返回一个列表,还能返回生成器、元组等。这个时候yield from表达式是对对象调用iter(),从而获取迭代器,因此,对象可以是任何可迭代的对象。
当然,yield from的功能肯定不止于此了!它的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样两者可以直接发送和产生值,还可以直接传入异常,而不用在位于中间的协程中添加大量的异常处理的样板代码。
我们现在就在yield from后面加上一个生成器,来实现生成器的嵌套。当然,实现这个生成器嵌套不一定要用yield from来实现,但是yield from可以让我们避免处理各种料想不到的异常,而让我们专注于业务代码的实现。
下面是几个概念!
1、调用方:调用委派生成器的客户端(调用方)代码
2、委托生成器:包含yield from表达式的生成器函数
3、子生成器:yield from后面加的生成器函数
下面这个示例来自《流畅的python》这本书,我觉得它比较好的解释了这三者的关系!
# 子生成器
def averager():
total = 0
count = 0
average = 0
while True:
new_num = yield average
count += 1
total += new_num
average = total/count
# 委派生成器
def grouper():
while True:
yield from averager()
# 调用方
def main():
calc_average = grouper()
next(calc_average) # 预激下生成器
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
if __name__ == '__main__':
main()
委托生成器的作用是:在调用方与子生成器之间建立一个双向通道。
所谓的双向通道是什么意思呢?
调用方可以通过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。
你可能会经常看到有些代码,还可以在yield from前面看到可以赋值。这是什么用法?
你可能会以为,子生成器yield回来的值,被委托生成器给拦截了。你可以亲自写个demo运行试验一下,并不是你想的那样。
因为我们之前说了,委托生成器,只起一个桥梁作用,它建立的是一个双向通道,它并没有权利也没有办法,对子生成器yield回来的内容做拦截。
为了解释这个用法,我还是用上述的例子,并对其进行了一些改造。添加了一些注释,希望你能看得明白。