异步等待的 Python 协程

如今 Python 已经支持用协程进行异步处理。但最近有建议称添加协程以全面完善 Python 的语言结构,而不是像如今这样把他们做为生成器的一个类型。此外,两个新的关键字———异步(async)和等待(await),都该添加到 Python 中来支持协程。html

异步等待的 Python 协程

也许有人不太了解协程,其实协程的原理很简单,打个比方就能讲明白了:假设有十我的去食堂打饭,这个食堂比较穷,只有一个打饭窗口和一个打饭阿姨,那么打饭就只能一个一个排队进行。这十我的胃口很大,每一个人都要点5个菜,但这十我的都喜欢犹豫不决,点菜的时候每点一个菜后再想下一个菜点什么,所以后面的人等得很着急呀。python

这样一直站着也不是个事儿,因此打菜的阿姨看到某人犹豫5秒后就会吼一声,让他排到队伍末尾,让别人先打菜,等轮到他的时候他也差很少想好吃什么了。这确实是个不错的方法,但也有一个缺点,那就是打菜的阿姨会等每一个人5秒钟,若是那我的在5秒内没有作出决定吃啥,其实这5秒就浪费了。一我的点一个菜就是浪费5秒,十我的每一个人点5个菜可就浪费的多啦「菜都凉了要」。session

那怎么办呢?阿姨又发话了:你们都是学生,学生就要自觉,我之后也不主动让大家排到末尾了,若是大家以为本身会犹豫不决,就主动点直接点一个菜就站后面去,等下次排到的时候也差很少想好吃啥了。异步

这个方法果真有效,你们点了菜后想的第一件事情不是下一个菜吃什么,而是本身会不会犹豫,若是会犹豫那直接排到队伍后面去,若是不会就接着点菜。这样一来整个队伍的效率天然就高了。async

这个例子里,排队阿姨的那声吼就是咱们的 CPU 中断,用于切换上下文。每一个打饭的学生就是一个 task。而每一个人决定本身要不要让出窗口的这种行为,其实就是咱们协程的核心思想。ide

OK,回到主题,协程就是一种能够在代码的各类预约义位置暂停和恢复执行的函数,它避免了无心义的调度,由此提升代码性能。而子程序是一种特殊的协同程序,它只有单一入口,经过回调来完成执行。Python 的协程「现有的以生成器为基础的协程和新提出的协程」不是通常意义上的协程,由于在执行暂停时它们只能将控制权转给调用者,而不是像常见的那样将控制权转给别的协程。辅之以事件循环,协程可用于异步处理,尤为是在 I / O 中。函数

Python 当前支持的协程基于 PEP342 加强型生成器,于 Python 2.5 版本开始采用。该 PEP 将 yield 语句改成表达式,并为生成器增长了一些新的方法 「 send() , throw() , and close() 」 ,同时确保 close() 方法在生成器进入垃圾回收阶段时获得调用。该功能在 Python 3.3 版本的 PEP 380 中获得进一步加强,它经过增长 yield 表达式,容许生成器将部分功能授予另外一个生成器「即子生成器」。性能

以上方法都使协程依赖于生成器,这使得在代码段何处进行异步调用变得使人困惑,且颇受限制。尤为,with 和 for 声明在理论上能够将协程用于异步调用,但 Python 语法在那些位置不容许使用 yield 表达式,所以没法进行异步调用。此外,若是协程的重构将 yield 或 yield from 从函数中移除 ,它就再也不被视为协程,这会致使一些不明显的错误; asyncio 模块经过 @asyncio.coroutine 装饰器来弥补这方面的不足。fetch

PEP 492 旨在解决以上全部问题。其想法源于 Yury Selivanov 在四月中旬提出的 python-ideas 邮件列表,该想法受到不少人热情追捧。在5月5日,Guido van Rossum 赞成将它添加在 Python 3.5 版本中。不只如此,5月12日就获得执行。一切都进展迅速,尽管最终该方法仍是在 python-idea 和 python-dev 方面引发热情讨论。ui

从语法角度看,变化至关简单:

async def read_data(db):
        data = await db.fetch('SELECT ...')
	...

这个例子「来源于 PEP」将使用新的 async def 构造函数建立一个 read_data() 协程。 await 表达式将暂停执行 read_data(),直到 db.fetch() await able 完成并返回其结果。await 相似于 yeild from ,但它会确保其参数 awaitable。

此外还有几种不一样类型的 awaitable。一种是本地的协程对象,在调用本地协同程序后的返回为 awaitable,还有基于生成器且有 @types.coroutine 装饰的协程。还有一种是将来对象,它表明着在将来完成的操做,也是 awaitable。__await __()方法在 awaitable 的对象都会出现。

然而,向一种语言添加新的关键字时会出现这样的问题:任何与关键字名字相同的变量都会成为语法错误。为了不该问题,Python 3.5 和 3.6 版本将 “softly deprecate “ 「温柔弃用」 async 和 wait 为变量名,而不将他们当作语法错误。解析器会跟踪 async def 块,并将块内的关键字区别对待,从而使现有的使用继续有效。

新的特性中,异步还有两种新用途:异步内容管理器(with)和迭代器(for)。在协程里,这两种构造函数的示例以下:

async def commit(session, data):
	...

	async with session.transaction():
	    ...
	    await session.update(data)
	    ...
        ...
        async for row in Cursor():
            print(row)

异步内容管理器必须实现两个异步方法,__aenter __()__aexit __(),他们都返回 awaitables;异步迭代器须实现__aiter __()__anext __()。这些方法都是现有的同步内容管理器和迭代器的异步版本。

此前主要的讨论是延期执行的 “cofunction” 功能 PEP 3152 是否会是更好的起点,该 PEP 的做者 Greg Ewing 提出了此问题。但有不少人认为 Selivanov 提议的语法更适合 codef,cocall ,也有人更加赞同 Ewing 的提议。这样来来回回的争论了不少次。有一些人认为cofunction 的语法在处理某些状况时至关复杂而且不符合 Python 语言的特性。后来 Van Rossum 总结了 cofunctions 语法存在的问题,并拒绝采纳该方法。

此外,还有几点关于附加异步功能的建议值得讨论,但并不紧急。对于关键词的讨论有些本末倒置。 await 的优先级问题也讨论了一段时间,结果是,不一样于 yeild 和 yeild from 仅有最低优先级,await 具备较高的优先级。

但 Mark Shannon 抱怨说,实现 Selivanov 的建议并不须要增长新的语法。其余人也提出了相似的意见,但 Selivanov 或其余支持者并未对此提出反驳。关键在于简化协同程序的编写。除此以外,Van Rossum 但愿协同程序暂停的位置可以显而易见,查看代码就能发现:

新的语法才是 PEP 存在的意义。我但愿经过句法结构就能判断出协程的悬停点。

在两三周后,发布了多个版本的 PEP ,引发了诸多辩论。Selivanov 耐心地解释他的想法,并根据反馈意见不断修正本身的想法。异步协程特性对 Python 语言的将来极可能相当重要,整个探索过程都很快,很顺遂。不过,Python 开发者们将这些想法付诸实践极可能还须要一段时间。

原文地址:Python coroutines with async and await

参考文章: 对Python中yield和协程的理解

本文系 OneAPM 工程师翻译。OneAPM 是应用性能管理领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客