本文参考了
- How the heck does async/await work in Python 3.5?
- PEP 380: Syntax for Delegating to a Subgenerator
yield 和 yield from
先让我们来学习或者回顾一下yield和yield from的用法。如果你很自信自己完成理解了可以跳到下一部分。
Python3.3提出了一种新的语法yield from。
yield from iterator
本质上也就相当于
for x in iterator:yield x
下面的这个例子中两个 yield from加起来就组合得到了一个大的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]
理解 yield from对于接下来的部分至关重要。想要完全理解 yield from还是来看看官方给的例子
def accumulate():tally 0while 1:next yieldif next is None:return tallytally nextdef 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 valuesfor i in range(4):acc.send(i)acc.send(None) # Finish the first tallyfor i in range(5):acc.send(i)acc.send(None) # Finish the second tallyprint(tallies)
我还专门为此录制了一段视频你可以配合文字一起看或者你也可以打开 pycharm 以及任何调试工具自己调试一下。 视频链接
来一起 break down
从acc gather_tallies(tallies)这一行开始由于gather_tallies函数中有一个 yield所以不会while 1立即执行(你从视频中可以看到acc 是一个 generator 类型)。
next(acc)
next()会运行到下一个 yield或者报StopIteration错误。
next(acc)进入到函数体gather_talliesgather_tallies中有一个yield from accumulate()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 来决定要不要退出循环。在for i in range(4)这个for循环里面i 都不为 None所以 while 循环没有断。但是根据我们前面讲的next()会运行到下一个 yield的地方停下来这个 while 循环一圈又再次遇到了yield所以他会暂停这个函数把控制权交还给主线程。
理清一下对于accumulate来说他的死循环是没有结束的下一次通过 next()恢复他运行时他还是在运行他的死循环。对于gather_tallies来说他的yield from accumulate()也还没运行完。对于整个程序来说确实在主进程和accumulate函数体之间进行了多次跳转。
接下来看第一个acc.send(None)这时next变量的值变成了Noneif next is None条件成立然后返回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的值为100 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?
这不就是生成器吗
python2.2 - 生成器起源
Python生成器的概念最早起源于 python2.2(2001年)时剔除的 pep255受Icon 编程语言启发。
生成器有一个好处不浪费空间看下面这个例子
def eager_range(up_to):"""Create a list of integers, from 0 to up_to, exclusive."""sequence []index 0while index
借助上一部分讲的yield稍作修改
def lazy_range(up_to):"""Generator to return the sequence of integers from 0 to up_to, exclusive."""index 0while index
python2.5 : send stuff back
一些有先见之明的前辈想到如果我们能够利用生成器能够暂停的这一特性然后想办法添加 send stuff back 的功能这不就符合维基百科对于协程的定义了么
于是就有了pep342。
pep342中提到了一个send()方法允许我们把一个"stuff"送回生成器里面让他接着运行。来看下面这个例子
def jumping_range(up_to):"""Generator for the sequence of integers from 0 to up_to, exclusive.Sending a value into the generator will shift the sequence by that amount."""index 0while index
python3.3 yield from
自从Python2.5之后关于生成器就没做什么大的改进了直到 Python3.3时提出的pep380。这个 pep 提案提出了yield from这个可以理解为语法糖的东西使得编写生成器更加简洁
def lazy_range(up_to):"""Generator to return the sequence of integers from 0 to up_to, exclusive."""index 0def gratuitous_refactor():nonlocal indexwhile index
python3.4 asyncio模块
插播事件循环eventloop
如果你有 js 编程经验肯定对事件循环有所了解。
理解一个概念最好也是最有bigger的就是翻出 wikipedia
an event loop "is a programming construct that waits for and dispatches events or messages in a program" - 来源于Event loop - wikipedia
简单来说eventloop 实现当 A 事件发生时做 B 操作。拿浏览器中的Javascript事件循环来说你点击了某个东西A 事件发生了就会触发定义好了的onclick函数做 B 操作。
在 Python 中asyncio 提供了一个 eventloop回顾一下上一篇的例子asyncio 主要聚焦的是网络请求领域这里的『A 事件发生』主要就是 socket 可以写、 socket可以读(通过selectors模块)。
到这个时期Python 已经通过Concurrent programming的形式具备了异步编程的实力了。
Concurrent programming只在一个 thread 里面执行。go 语言blog 中有一个非常不错的视频Concurrency is not parallelism很值得一看。
这个时代的 asyncio 代码
这个时期的asyncio代码是这样的
import asyncio# Borrowed from http://curio.readthedocs.org/en/latest/tutorial.html.asyncio.coroutinedef countdown(number, n):while n > 0:print(T-minus, n, ({}).format(number))yield from asyncio.sleep(1)n - 1loop asyncio.get_event_loop()tasks [asyncio.ensure_future(countdown("A", 2)),asyncio.ensure_future(countdown("B", 3))]loop.run_until_complete(asyncio.wait(tasks))loop.close()
输出结果为
T-minus 2 (A)T-minus 3 (B)T-minus 1 (A)T-minus 2 (B)T-minus 1 (B)
这时使用的是asyncio.coroutine修饰器,用来标记某个函数可以被 asyncio 的事件循环使用。
看到yield from asyncio.sleep(1)了吗通过对一个asyncio.Future object yield from就把这个future object 交给了事件循环当这个 object 在等待某件事情发生时这个例子中就是等待 asyncio.sleep(1)等待 1s 过后把函数暂停开始做其他的事情。当这个future object 等待的事情发生时事件循环就会注意到然后通过调用send()方法让它从上次暂停的地方恢复运行。
break down 一下上面这个代码
事件循环开启了两个countdown()协程调用一直运行到yield from asyncio.sleep(1)这会返回一个 future object然后暂停接下来事件循环会一直监视这两个future object。1秒过后事件循环就会把 future object send()给coroutinecoroutine又会接着运行打印出T-minus 2 (A)等。
python3.5 async await
python3.4的
asyncio.coroutinedef py34_coro():yield from stuff()
到了 Python3.5可以用一种更加简洁的语法表示
async def py35_coro():await stuff()
这种变化从语法上面来讲并没什么特别大的区别。真正重要的是是协程在 Python 中哲学地位的提高。 在 python3.4及之前异步函数更多就是一种很普通的标记修饰器在此之后协程变成了一种基本的抽象基础类型abstract base classclass collections.abc.Coroutine。
How the heck does async/await work in Python 3.5?一文中还讲到了async、await底层 bytecode 的实现这里就不深入了毕竟篇幅有限。
把 async、await看作是API 而不是 implementation
Python 核心开发者也是我最喜欢的 pycon talker 之一David M. Beazley在PyCon Brasil 2015的这一个演讲中提到我们应该把 async和await看作是API而不是实现。 也就是说async、await不等于asyncioasyncio只不过是async、await的一种实现。当然是asyncio使得异步编程在 Python3.4中成为可能从而推动了async、await的出现
他还开源了一个项目github.com/dabeaz/curi…底层的事件循环机制和 asyncio 不一样asyncio使用的是future objectcurio使用的是tuple。同时这两个 library 有不同的专注点asyncio 是一整套的框架curio则相对更加轻量级用户自己需要考虑到事情更多。
How the heck does async/await work in Python 3.5?此文还有一个简单的事件循环实现例子有兴趣可以看一下后面有时间的话也许会一起实现一下。
总结一下 - 协程只有一个 thread。
- 操作系统调度进程、协程用事件循环调度函数。
- async、await 把协程在 Python 中的哲学地位提高了一个档次。
最重要的一点感受是Nothing is Magic。现在你应该能够对 Python 的协程有了在整体上有了一个把握。
如果你像我一样真正热爱计算机科学喜欢研究底层逻辑欢迎关注我的微信公众号
【文章转自:美国站群服务器 http://www.558idc.com/mgzq.html处的文章,转载请说明出处】