本文参考了:html
先让咱们来学习或者回顾一下yield
和yield from
的用法。若是你很自信本身完成理解了,能够跳到下一部分。python
Python3.3提出了一种新的语法:yield from
。git
yield from iterator
复制代码
本质上也就至关于:github
for x in iterator:
yield x
复制代码
下面的这个例子中,两个 yield from
加起来,就组合获得了一个大的iterable
(例子来源于官网3.3 release):golang
>>> 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 = 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:bash
从acc = gather_tallies(tallies)
这一行开始,因为gather_tallies
函数中有一个 yield,因此不会while 1
当即执行(你从视频中能够看到,acc 是一个 generator 类型)。微信
next(acc)
:网络
next()会运行到下一个 yield,或者报StopIteration错误。
next(acc)
进入到函数体gather_tallies,gather_tallies中有一个yield from accumulate()
,next(acc)不会在这一处停,而是进入到『subgenerator』accumulate里面,而后在next = yield
处,遇到了yield
,而后暂停函数,返回。
for i in range(4):
acc.send(i)
复制代码
理解一下 acc.send(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
变量的值变成了None
,if 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的值为10(0 + 1 + 2 + 3 + 4)。
最终tallies列表为:[6,10]
。
看一下 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?)
这不就是生成器吗?
Python生成器的概念最先起源于 python2.2(2001年)时剔除的 pep255,受Icon 编程语言启发。
生成器有一个好处,不浪费空间,看下面这个例子:
def eager_range(up_to):
"""Create a list of integers, from 0 to up_to, exclusive."""
sequence = []
index = 0
while index < up_to:
sequence.append(index)
index += 1
return sequence
复制代码
若是用这个函数生成一个10W 长度的列表,须要等待 while 循环运行结束返回。而后这个sequence
列表将会占据10W 个元素的空间。耗时不说(从可以第一次可以使用到 sequence 列表的时间这个角度来看),占用空间还很大。
借助上一部分讲的yield
,稍做修改:
def lazy_range(up_to):
"""Generator to return the sequence of integers from 0 to up_to, exclusive."""
index = 0
while index < up_to:
yield index
index += 1
复制代码
这样就只须要占据一个元素的空间了,并且当即就能够用到 range,不须要等他所有生成完。
一些有先见之明的前辈想到,若是咱们可以利用生成器可以暂停的这一特性,而后想办法添加 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 = 0
while index < up_to:
jump = yield index
if jump is None:
jump = 1
index += jump
if __name__ == '__main__':
iterator = jumping_range(5)
print(next(iterator)) # 0
print(iterator.send(2)) # 2
print(next(iterator)) # 3
print(iterator.send(-1)) # 2
for x in iterator:
print(x) # 3, 4
复制代码
这里的send
把一个『stuff』送进去给生成器,赋值给 jump,而后判断jump 是否是 None,来执行对应的逻辑。
自从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 = 0
def gratuitous_refactor():
nonlocal index
while index < up_to:
yield index
index += 1
yield from gratuitous_refactor()
复制代码
第一节咱们已经详细讲过 yield from 了,这里就不赘述了。
若是你有 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代码是这样的:
import asyncio
# Borrowed from http://curio.readthedocs.org/en/latest/tutorial.html.
@asyncio.coroutine
def countdown(number, n):
while n > 0:
print('T-minus', n, '({})'.format(number))
yield from asyncio.sleep(1)
n -= 1
loop = 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()给coroutine,coroutine又会接着运行,打印出T-minus 2 (A)
等。
python3.4的
@asyncio.coroutine
def py34_coro():
yield from stuff()
复制代码
到了 Python3.5,能够用一种更加简洁的语法表示:
async def py35_coro():
await stuff()
复制代码
这种变化,从语法上面来说并没什么特别大的区别。真正重要的是,是协程在 Python 中哲学地位的提升。 在 python3.4及以前,异步函数更多就是一种很普通的标记(修饰器),在此以后,协程变成了一种基本的抽象基础类型(abstract base class):class collections.abc.Coroutine。
How the heck does async/await work in Python 3.5?一文中还讲到了async
、await
底层 bytecode 的实现,这里就不深刻了,毕竟篇幅有限。
Python 核心开发者(也是我最喜欢的 pycon talker 之一)David M. Beazley在PyCon Brasil 2015的这一个演讲中提到:咱们应该把 async
和await
看做是API,而不是实现。 也就是说,async
、await
不等于asyncio
,asyncio
只不过是async
、await
的一种实现。(固然是asyncio
使得异步编程在 Python3.4中成为可能,从而推进了async
、await
的出现)
他还开源了一个项目github.com/dabeaz/curi…,底层的事件循环机制和 asyncio
不同,asyncio
使用的是future object
,curio
使用的是tuple
。同时,这两个 library 有不一样的专一点,asyncio
是一整套的框架,curio
则相对更加轻量级,用户本身须要考虑到事情更多。
How the heck does async/await work in Python 3.5?此文还有一个简单的事件循环实现例子,有兴趣能够看一下,后面有时间的话也许会一块儿实现一下。
最重要的一点感觉是:Nothing is Magic。如今你应该可以对 Python 的协程有了在总体上有了一个把握。
若是你像我同样真正热爱计算机科学,喜欢研究底层逻辑,欢迎关注个人微信公众号: