python awaitable_[翻译] Python 3.5中async/await的工作机制

Python 3.5中async/await的工作机制

多处翻译出于自己理解,如有疑惑请参考原文 原文链接

身为Python核心开发组的成员,我对于这门语言的各种细节充满好奇。尽管我很清楚自己不可能对这门语言做到全知全能,但哪怕是为了能够解决各种issue和参与常规的语言设计工作,我也觉得有必要试着接触和理解Python的内核,弄清楚在底层它是怎么工作的。

话虽如此,直到最近我才理解了Python3.5中async/await的工作机制。在此之前,对于async/await语法,我只知道Python3.3中的yield from和Python3.4中的asyncio让这个新语法得以在Python3.5中实现。由于日常工作中没有接触多少网络编程--asyncio的主要应用领域,虽然它可以做的远不止于此--我对async/await并没有关注太多。以代码来说,我知道:

yield from iterator

(大体)等价于:

from x in iterator:

yield x

而且我知道asyncio是个事件循环的框架,支持异步编程,还有这些术语所表示的(基本)意义。但未曾真正的深入研究async/await语法,分析从最基础的指令到实现代码语法功能的过程,我觉得并没有理解Python中的异步编程,这一点甚至让我心烦意乱。因此我决定花点时间弄明白这个语法的工作机制。鉴于我听到许多人说他们也不理解异步编程的工作机制,我写出了这篇论文(是的,这篇博文耗费时间之长,字数之多,让我妻子把它叫做论文)。

由于我希望对这个语法的工作机制有一个完整的理解,这篇论文中会出现涉及CPython的底层技术细节。如果你不关心这些细节,或者无法通过这篇文章完全理解这些细节--限于篇幅,我不可能详细解释CPython的每个细节,否则这篇文章就要变成一本书了(例如,如果你不知道代码对象具有标识位,那就别在意代码对象是什么,这不是这篇文章的重点)--那也没什么关系。在每个章节的最后,我都添加了一个概念明确的小结,因此如果你对某个章节的内容不感兴趣,那么可以跳过前面的长篇大论,直接阅读结论。

Python中协程(coroutine)的历史

根据维基百科,“协程是将多个低优先级的任务转换成统一类型的子任务,以实现在多个节点之间停止或唤醒程序运行的程序模块”。这句专业论述翻译成通俗易懂的话就是,“协程就是可以人为暂停执行的函数”。如果你觉得,“这听起来像是生成器(generators)”,那么你是对的。

生成器的概念在Python2.2时的PEP 255中(由于实现了遍历器的协议,生成器也被成为生成器遍历器)第一次被引入。主要受到了Icon语言的影响,生成器允许用户创建一个特殊的遍历器,在生成下一个值时,不会占用额外的内存,并且实现方式非常简单(当然,在自定义类中实现__iter__()和__next__()方法也可以达到不存储遍历器中所有值的效果,但也带来了额外的工作量)。举例来说,如果你想实现自己的range()函数,最直接的方式是创建一个整数数组:

def eager_range(up_to):

"""创建一个从0到变量up_to的数组,不包括up_to"""

sequence = []

index = []

while index < up_to:

sequence.append(index)

index += 1

return sequence

简单直白,但这个函数的问题是,如果你需要的序列很大,比如0到一百万,你必须创建一个包含了所有整数的长度是一百万的数组。如果使用生成器,你就可以毫不费力的创建一个从0到上限前一个整数的生成器。所占用的内存也只是每次生成的一个整数。

def lazy_range(up_to):

"""一个从0到变量up_to,不包括up_to的生成器"""

index = 0

while index < up_to:

yield index

index += 1

函数可以在遇到yield表达式时暂停执行--尽管yield直到Python2.5才出现--然后在下次被调用时继续执行,这种特性对于节约内存使用有意义深远,可以用于实现无限长度的序列。

也许你已经注意到了,生成器所操作的都是遍历器。多一种更好的创建遍历器的语法的确不错(当你为一个对象定义__iter__()方法作为生成器时,也会收到类似的提升),但如果我们把生成器的“暂停”功能拿出来,再加上“把事物传进去”的功能,Python就有了自己的协程功能(暂且把这个当成Python的一个概念,真正的Python中的协程会在后面详细讨论)。Python 2.5中引入了把对象传进一个被暂停的生成器的功能,这要归功于PEP 342。抛开与本文无关的内容不看,PEP 342引入了生成器的send()方法。这样就不光可以暂停生成器,更可以在生成器停止时给它传回一个值。在上文range()函数的基础上更近一步,你可以让函数产生的序列前进或后退:

def jumping_range(up_to):

"""一个从0到变量up_to,不包括up_to的生成器

传入生成器的值会让序列产生对应的位移

"""

index = 0

while index < up_to:

jump = yield index

if jump is not 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

直到Python 3.3中PEP 380引入yield from之前,生成器都没有太大的变化。严格的说,yield from让用户可以轻松便捷的从遍历器(生成器最常见的应用场景)里提取每一个值࿰

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值