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

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

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

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

这个方法果然有效,大家点了菜后想的第一件事情不是下一个菜吃什么,而是自己会不会犹豫,如果会犹豫那直接排到队伍后面去,如果不会就接着点菜。这样一来整个队伍的效率自然就高了。

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

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 装饰器来弥补这方面的不足。

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

从语法角度看,变化相当简单:

    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 官方博客

异步等待的 Python 协程的更多相关文章

  1. day-5 python协程与I/O编程深入浅出

    基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1.  什么是协程(以下内容来自维基百 ...

  2. Python协程(真才实学,想学的进来)

    真正有知识的人的成长过程,就像麦穗的成长过程:麦穗空的时候,麦子长得很快,麦穗骄傲地高高昂起,但是,麦穗成熟饱满时,它们开始谦虚,垂下麦芒. --蒙田<蒙田随笔全集> *** 上篇论述了关 ...

  3. [转载] Python协程从零开始到放弃

    Python协程从零开始到放弃 Web安全 作者:美丽联合安全MLSRC   2017-10-09  3,973   Author: lightless@Meili-inc Date: 2017100 ...

  4. PHP下的异步尝试三:协程的PHP版thunkify自动执行器

    PHP下的异步尝试系列 如果你还不太了解PHP下的生成器和协程,你可以根据下面目录翻阅 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunk ...

  5. python协程总结

    概述 python多线程中因为有GIL(Global Interpreter Lock 全局解释器锁 )的存在,所以对CPU密集型程序显得很鸡肋:但对IO密集型的程序,GIL会在调用IO操作前释放,所 ...

  6. Python协程与Go协程的区别二

    写在前面 世界是复杂的,每一种思想都是为了解决某些现实问题而简化成的模型,想解决就得先面对,面对就需要选择角度,角度决定了模型的质量, 喜欢此UP主汤质看本质的哲学科普,其中简洁又不失细节的介绍了人类 ...

  7. python 协程与go协程的区别

    进程.线程和协程 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定义: 操作系统能够进行运算调度的最小单位.它被包含在进 ...

  8. python协程详解,gevent asyncio

    python协程详解,gevent asyncio 新建模板小书匠 #协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1.生成器实现切换 [1] ...

  9. Python核心技术与实战——十六|Python协程

    我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...

随机推荐

  1. lib和dll的区别,生成(转)

    首先介绍下静态库(静态链接库,.lib文件),动态库*(动态链接库,.dll文件)的概念,首先两者都是代码共享的方式. 静态库:在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件,这 ...

  2. SQL 集合(笔记)

    ——SQL是关于集合的 oracle是关系型数据,其中的数据表都是有一定规律的数据的一个个集合,所以在使用SQL时,如果能按照集合的思路来进行时会节省很多效率,也鞥让语句更加的清晰明了. 1.四个集合 ...

  3. exception -----> Typedefs & Classes

    #include <exception> Typedefs exception_ptr 一种类型,描述了一个指向异常的指针 terminate_handler 一种类型,描述了一个适合作为 ...

  4. C# SQL增删查改

    DBHelper: /// <summary> /// 执行查询 /// </summary> /// <param name="sql">有效 ...

  5. 失败的数据库迁移UDB

    公司采用的是ucloud的云主机,数据库也是架设在云主机上.由于数据越来越多数据查询数据越来越慢,所以我决定往 UDB上迁移.当时考虑的理由如下: (1)云主机底层架设在虚拟机上IO性能有折损,而UD ...

  6. algorithm之不变序列操作

    概述:不变序列算法,参见http://www.cplusplus.com/reference/algorithm/ /* std::for_each template <class InputI ...

  7. Daily Scrum4

    今天我们小组开会内容分为以下部分: part1:与负责这个项目的其他组进行会晤; part2:组内成员召开了简短会议,进行工作安排; part3:总结今日工作,对项目遇到的问题商讨解决办法; ◆Par ...

  8. Notes of the scrum meeting(12.8)

    meeting time:18:00~18:30p.m.,December 8th,2013 meeting place:20号公寓前 attendees: 顾育豪                   ...

  9. 简单的C语言小学四则运算设计

    题目:设计一个简单的四则运算编辑器 思路:我使用的是C语言编程,看到题目首先要随机出3个随机数,其中两个为100以内的随机数(a,b),一个为0~3的随机数(k). k值的变化使得+ - * /的变化 ...

  10. 关于js with语句的一些理解

    关于js with语句的一些理解   今天看到js的with语句部分,书中写到,with语句接收的对象会添加到作用域链的前端并在代码执行完之后移除.看到这里,我有两点疑问,添加到作用域链前端是不是指对 ...