python yield、yield from与协程
从生成器到协程
协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。生成器的调用方可以使用 .send(...)方法发送数据,发送的数据会成为yield表达式的值。因此,生成器可以作为协程使用。
从句法上看,生成器与协程都是包含yield关键字的函数。但是,在协程中,yield通常出现在表达式的右边(* = yield *),可以产出值也可以不产出(yield关键字后边没有表达式,产出None)。
协程有四个状态:
GEN_CREATED:等待开始执行
GEN_RUNNING:正在执行(只有在多线程应用或生成器对象自身调用getgeneratorstate函数可以看到此状态)
GEN_SUSPENDED:在yield表达式处阻塞
GEN_CLOSED:执行结束
使用inspect.getgeneratorstate(...)函数可以查看当前协程的状态。
使用协程的基本步骤为:
- 创建协程对象
- 调用next函数,激活协程
- 调用 .send(...)方法,推动协程执行并产出
一个累积求和的协程示例如下:

如上图示例所示,协程中产出的值会返回给调用方,同时,通过yield将调用方传入的参数赋值给yield表达式左边的变量,并推动协程继续执行。
终止协程和异常处理
因为协程使用生成器函数定义,因此遵循生成器的特性,当协程执行到定义体末尾时,会抛出StopIteration异常。如果协程在执行过程中发生了未处理的异常,协程会终止运行并将异常抛出,此时,试图重新激活协程会抛出StopIteration异常。代码示例:


示例代码中,依然使用累积求和的协程,调用时因为传入了字符串参数,导致协程因TpyeError异常而终止,再次试图调用时,抛出了StopIteration异常。
调用方可以通过调用生成器对象 .throw(exc_type[, exc_value[, traceback]])方法,致使生成器在阻塞的yield表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个yield表达式,产出的表达式会成为 .throw()方法的返回值;如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。代码示例:

上图示例代码中,协程对TypeError进行了处理,所以当调用方将TpyeError异常发给协程时没有终止;而当调用方将ValueError发给协程时,由于没有处理,协程终止并将异常向上抛给调用方处理,调用方虽然捕获了该异常,但试图再次调用协程时,由于协程已终止,故抛出了StopIteration异常。
调用方可以通过生成器对象的 .close()方法,致使生成器在阻塞的yield表达式处抛出GeneratorExit异常。如果生成器没有处理这个异常,或者抛出了StopIteration异常(通常指运行到程序结尾),调用方不会报错。代码示例:

上图示例代码中,调用 .close()方法后,调用方没有报错,协程终止且返回值为None,试图再次激活协程对象时,会抛出StopIteration异常。
需要注意的是:如果在协程中捕获了GeneratorExit异常,会导致RuntimeError;如果使用 .throw()方法直接将GeneratorExit异常发给协程,调用方会报错并导致GeneratorExit异常。
让协程返回值
有些协程不会产出值,而是在执行结束后返回一个值,而为了返回这个值,协程必须正常终止。代码示例:

上图示例中,协程不再产出值,通过send(None)结束协程,代码执行到最后触发StopIteration异常,而返回值作为StopIteration异常的一个属性返回给调用方。
yield from
yield from是全新的语言结构,多用于嵌套生成器。其主要功能是开辟一个双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以发送/产出值,还可以直接传入异常,而不用在位于中间层的协程中添加大量处理异常的代码。简言之即yield from可以方便的实现生成器嵌套调用并自动处理大部分异常。
理解yield from首先要理解三个概念:
- 调用方:指委派生成器的客户端代码
- 委派生成器:包含yield from <iterable>表达式的生成器函数
- 子生成器:从yield from表达式中<iterable>部分获取的生成器
典型的调用逻辑为:客户端代码(调用方)调用委派生成器对象,委派生成器在yield from表达式处阻塞,此时调用方与子生成器之间的双向通道打开,调用方可以直接把数据发给子生成器,子生成器把产出的值发给调用方。子生成器执行结束,解释器抛出StopIteration异常,并把返回值附加到异常对象上,此时委派生成器恢复执行。委派生成器yield from语句自动处理子生成器抛出的StopIteration异常及附加在异常对象上的返回值。代码示例如下:

注意:委派生成器执行结束时也会抛出StopIteration异常,这里使用了永久循环+全局变量(不推荐)的方式避免委派生成器退出引发StopIteration异常且使客户端能够拿到子生成器返回的结果。实际应用中应视情况进行异常处理。子生成器StopIteration之外未处理的异常会向上冒泡传给委派生成器处理,yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数。python3.5以后引入了await关键字来替代yield from,使代码更加简洁清晰。
以上。
python yield、yield from与协程的更多相关文章
- yield与send实现协程操作
yield与send实现协程操作 之前我们说过,在函数内部含有yield语句即称为生成器. 下面,我们来看看在函数内部含有yield语句达到的效果.首先,我们来看看以下代码: def foo(): w ...
- yield、greenlet与协程gevent
yield 在说明yield之前,我们了解python中一些概念. 在了解Python的数据结构时,容器(container).可迭代对象(iterable).迭代器(iterator).生成器(ge ...
- 深入理解协程(二):yield from实现异步协程
原创不易,转载请联系作者 深入理解协程分为三部分进行讲解: 协程的引入 yield from实现异步协程 async/await实现异步协程 本篇为深入理解协程系列文章的第二篇. yield from ...
- Python PEP 492 中文翻译——协程与async/await语法
原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...
- python之gevent模块实现协程
Python通过yield提供了对协程的基本支持,但是不完全.而第三方的gevent为Python提供了比较完善的协程支持. gevent是第三方库,通过greenlet实现协程,其基本思想是: 当一 ...
- Python的异步编程[0] -> 协程[0] -> 协程和 async / await
协程 / Coroutine 目录 生产者消费者模型 从生成器到异步协程– async/await 协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时 ...
- python并发编程之线程/协程
python并发编程之线程/协程 part 4: 异步阻塞例子与生产者消费者模型 同步阻塞 调用函数必须等待结果\cpu没工作input sleep recv accept connect get 同 ...
- python单线程,多线程和协程速度对比
在某些应用场景下,想要提高python的并发能力,可以使用多线程,或者协程.比如网络爬虫,数据库操作等一些IO密集型的操作.下面对比python单线程,多线程和协程在网络爬虫场景下的速度. 一,单线程 ...
- Python 线程和进程和协程总结
Python 线程和进程和协程总结 线程和进程和协程 进程 进程是程序执行时的一个实例,是担当分配系统资源(CPU时间.内存等)的基本单位: 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其 ...
- python并发编程之gevent协程(四)
协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...
随机推荐
- rocketmq配置项说明(对应版本:4.0.0-incubating)
Broker配置参数说明 自定义客户端行为 ※一些默认配置的源代码路径 org.apache.rocketmq.store.config --END--
- 转:开启命令行下的社交-webqq脚本
最近一直在命令行下工作,除了 Google Chrome,几乎很少接触 GUI 相关的软件.前段时间把手机上的 QQ 给卸载了,希望可以把时间凝聚在更加有价值的位置,今天突然又想起了这个软件,突发奇想 ...
- HDU 6162 Ch’s gift (线段树+树链剖分)
题意:给定上一棵树,每个树的结点有一个权值,有 m 个询问,每次询问 s, t , a, b,问你从 s 到 t 这条路上,权值在 a 和 b 之间的和.(闭区间). 析:很明显的树链剖分,但是要用 ...
- GithubPage自定义腾讯404界面
思路来源 之前看到腾讯网络的404和github自带的404,反差很大,于是想自己弄一个了. 过程 github的404一点也不复杂,只是需要在根目录添加一个404.html的文档,或者404.md的 ...
- 已经导入到VS工具箱中的DevExpress如何使用
1.下载安装DevExpress控件(如DXperienceUniversal-11.1.12.exe),安装后路径:“C:\Program Files (x86)\DevExpress 2011.1 ...
- 2、Semantic-UI之网格布局
2.1 网格布局 在semantic-ui中提供了16个网格,使用class="column",当然也可以通过数字来表示当前网格大小. 在Semantic-UI中定义的网格 ...
- Delphi 调试连接 任意Android手机/平板/盒子(要安装Google USB Driver,并且还有USB的相关许多文章)
Delphi有时候无法连接调试一些手机,解决方案: 1.安装Google USB Driver 2.通过设备管理器查看手机或平板USB的VID,PID 3.修改你的电脑上的android_winusb ...
- 不用SQL给打印记录编号
以QUICKREPORT为例 页面设置如下: 其中ID为编号. 设置为表的ID字段. QUICKREPORT所在的FORM添加一个变量: var FprnT6: TFprnT6; Vxh:intege ...
- android之实现底部TabHost
先说布局文件,如下:利用android:layout_alignParentBottom="true" 实现底部显示 <?xml version="1.0" ...
- WPF Viewport3D 解决透视模式时窗体模糊
最近折腾Viewport3D玩,遇到了一些诡异的问题,研究一下略有心得,特此和大家分享~ 三维图形概述: https://msdn.microsoft.com/zh-cn/library/ms7474 ...