非同步编程 101:Python async await发展简史
本文参考了:
- How the heck does async/await work in Python 3.5?
- PEP 380: Syntax for Delegating to a Subgenerator
yield 和 yield from
先让我们来学习或者回顾一下 yield
和 yieldfrom
的用法。如果你很自信自己完成理解了,可以跳到下一部分。
Python3.3提出了一种新的语法: yieldfrom
。
yield from iterator
本质上也就相当于:
for x in iterator:
yield x
下面的这个例子中,两个 yieldfrom
加起来,就组合得到了一个大的 iterable
(例子来源于官网3.3 release):
>>> def g(x):
... yield from range(x, 0, -1)
... yield from range(x)
...
>>> list(g(5))
[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
理解 yieldfrom
对于接下来的部分至关重要。想要完全理解 yieldfrom
,还是来看看官方给的例子:
def accumulate():
tally = 0
while 1:
next = yield
if next is None:
return tally
tally += next
def gather_tallies(tallies):
while 1:
tally = yield from accumulate()
tallies.append(tally)
tallies = []
acc = gather_tallies(tallies)
next(acc) # Ensure the accumulator is ready to accept values
for i in range(4):
acc.send(i)
acc.send(None) # Finish the first tally
for i in range(5):
acc.send(i)
acc.send(None) # Finish the second tally
print(tallies)
我还专门为此录制了一段视频,你可以配合文字一起看,或者你也可以打开 pycharm 以及任何调试工具,自己调试一下。 视频链接
来一起 break down:
从 acc=gather_tallies(tallies)
这一行开始,由于 gather_tallies
函数中有一个 yield,所以不会 while1
立即执行(你从视频中可以看到,acc 是一个 generator 类型)。
next(acc)
:
next()会运行到下一个 yield,或者报StopIteration错误。
next(acc)
进入到函数体gathertallies,gathertallies中有一个 yieldfromaccumulate()
,next(acc)不会在这一处停,而是进入到『subgenerator』accumulate里面,然后在 next=yield
处,遇到了 yield
,然后暂停函数,返回。
for i in range(4):
acc.send(i)
理解一下 acc.send(value)
有什么用:
- 第一步:回到上一次暂停的地方
- 第二步:把value 的值赋给
xxx=yield
中的xxx
,这个例子中就是next
。
accumulate
函数中的那个while 循环,通过判断 next
的值是不是 None 来决定要不要退出循环。在 foriinrange(4)
这个for循环里面,i 都不为 None,所以 while 循环没有断。但是,根据我们前面讲的:next()会运行到下一个 yield的地方停下来,这个 while 循环一圈,又再次遇到了 yield
,所以他会暂停这个函数,把控制权交还给主线程。
理清一下:对于accumulate来说,他的死循环是没有结束的,下一次通过 next()恢复他运行时,他还是在运行他的死循环。对于gather_tallies来说,他的
yieldfromaccumulate()
也还没运行完。对于整个程序来说,确实在主进程和accumulate函数体之间进行了多次跳转。
接下来看第一个 acc.send(None)
:这时 next
变数的值变成了 None
, ifnextisNone
条件成立,然后返回 tally
给上一层函数。(计算一下,tally 的值为0 + 1 + 2 + 3 = 6)。这个返回值就赋值给了 gather_tallies
中的 gally
。这里需要注意的是, gather_tallies
的死循环还没结束,所以此时调用 next(acc)
不会报 StopIteration
错误。
for i in range(5):
acc.send(i)
acc.send(None) # Finish the second tally
这一部分和前面的逻辑是一样的。acc.send(i)会先进入 gather_tallies
,然后进入 accumulate
,把值赋给 next
。 acc.send(None)
停止循环。最后tally的值为10(0 + 1 + 2 + 3 + 4)。
最终tallies列表为: [6,10]
。
Python async await发展简史
看一下 wikipedia 上 Coroutine的定义:
Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.
关键点在于by allowing execution to be suspended and resumed.(让执行可以被暂停和被恢复)。通俗点说,就是:
coroutines are functions whose execution you can pause。(来自How the heck does async/await work in Python 3.5?)