协程进化史:从yield到await

本文介绍了Python协程的发展历程,从yield/send时代的生成器,到yield from带来的简化和嵌套功能,再到asyncio库的引入以及async/await关键字的出现。通过示例展示了yield from如何在调用方与子生成器间建立双向通道,以及async/await如何简化异步编程。最后,探讨了asyncio事件循环和协程的状态管理。
摘要由CSDN通过智能技术生成

在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回来的内容做拦截。

为了解释这个用法,我还是用上述的例子,并对其进行了一些改造。添加了一些注释,希望你能看得明白。


                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值