python 的 async, await(协程)理解

python中的async/await and yield 以及yield from 的概念

首先说明以下:

① yield from 诞生于python3.3版本

② async/await 关键字 诞生于python3.5版本

以上两种都可以称为协程,也就是异步的实现方式之一。

我的版本为python3.6


一、yield from

首先最简单最原始的一种协程关键字

1.yield:等价于return,只不过yield具备惰性特性,惰性特性,有效的节省内存控件,当需要输出的时候才会输出。存在yield关键字的函数,被称作生成器函数。

2.yield from :yield关键字的升级版,后面可以接任何的可迭代对象(和下面的await有区别哦,下面会讲),表示要从哪里generate 元素出来,yield from generate,返回一个生成器。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def func():
# 通过循环产出i
for i in range(10):
if i >8: # 当i=8,就不在产出值了,此时会抛出StopIteration异常,如果用next(obj)产值的话,StopIteration后面会接'stop`
return 'stop'
yield i

print(func())
for i in func():
print(i)

def test():
# 相当对可迭代对象m的循环产出值
m = [9,8,7,6,5,4,3,2,1]
yield from m

print(test())
for i in test():
print(i)

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<generator object func at 0x000001EFAD21CF10>  # 拥有yield关键字的生成器函数
0
1
2
3
4
5
6
7
8
<generator object test at 0x000001EFAD21CF10> # yield from 的生成器函数
9
8
7
6
5
4
3
2
1

注:yield from相当于一层for循环,它会处理最后的异常StopIteration,如果函数中有return返回的话(需要next()显示抛出异常),通过yield from 会隐式处理该异常,因此并不会输出return 后的内容。因此如果要输入return 后的内容,则需要手动不断next()了~

最后for循环print(i),会内部循环调用next()方法,同时最后处理StopIteration异常。

在《流畅的python》一书中这样介绍了yield from:

选择书中的一幅图
{width=”100%”}

解释:

1.委派生成器在 yield from 表达式处暂停时,调用方可以直接把数据发给子生成器。

2.子生成器再把产出的值发给调用方。

3.子生成器返回之后,解释器会抛出 StopIteration 异常,并把返回值附加到异常对象上,此时委派生成器会恢复。

yield from 的主要作用:

是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。


二、async/await

async/await是一套的关键字,也就是他们俩一般一起使用。

async:用来声明一个函数为异步函数的关键字。

await:用来对程序进行挂起,转到其他异步程序中执行。然后执行完(也可能另外的异步程序也需要挂起)

简单的做菜小案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import time
import asyncio

async def clean_vegetable():
await asyncio.sleep(10)
print("我洗好了菜")

async def make_rice():
await asyncio.sleep(8)
print("我做好了米饭")

async def cook():
await asyncio.sleep(11)
print("我炖好了肉")

def async_eat():
start_time = time.clock()
loop = asyncio.get_event_loop()
tasks = [
clean_vegetable(),
make_rice(),
cook(),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print('我花了%s时间做好了一顿饭,可以吃饭了~'%(time.clock()-start_time))

运行结果:

1
2
3
4
5
6
异步执行:

我做好了米饭
我洗好了菜
我炖好了肉
我花了11.0029621时间做好了一顿饭

说明:
上述例子可以发现,采用异步执行只花了11s就做好了一顿饭,而如果我们采用同步执行,我们需要花费10+8+11=29s,快了一倍多。因为,我在做淘完米后,我就可以去洗菜,而不是在那一直等米饭熟。

注:

① 使用async关键字修饰的函数为指明为异步函数,执行到await将当前执行的程序挂起来,不经过系统调用切换到其他函数进行执行(这是与线程区别不同之一)。

② 这里使用了async.sleep(),而不是time.sleep(),因为async.sleep()的时间是可等待的,也就是”假睡眠”,可以进行函数间的切换而time.sleep()是真睡眠。