Python3中的yield from语法

by Kay Zheng 
Tags: python, 协程, generator 
30 March 2014

2016-2-23 更新

這篇文章是兩年前寫的, Python 3 中的 coroutine 現在使用的是 PEP-492 提出的 async/await 語法,詳情請見我的另一篇文章。

緣起

最近在捣鼓Autobahn,它有给出个例子是基于asyncio 的,想着说放到pypy3上跑跑看竟然就……失败了。 pip install asyncio直接报invalid syntax,粗看还以为2to3处理的时 候有问题——这不能怪我,好~多package都是用2写了然后转成3的——结果发 现asyncio本来就只支持3.3+的版本,才又回头看代码,赫然发现一句 yield fromyield我知道,但是yield from是神马?

PEP-380

好吧这个标题是我google出来的,yield from的前世今生都在 这个PEP里面,总之大意是原本的yield语句只能将CPU控制权 还给直接调用者,当你想要将一个generator或者coroutine里带有 yield语句的逻辑重构到另一个generator(原文是subgenerator) 里的时候,会非常麻烦,因为外面的generator要负责为里面的 generator做消息传递;所以某人有个想法是让python把消息传递 封装起来,使其对程序猿透明,于是就有了yield from

PEP-380规定了yield from的语义,或者说嵌套的generator应该 有的行为模式。

假设A函数中有这样一个语句

yield from B()

B()返回的是一个可迭代(iterable)的对象b,那么A()会返回一个 generator——照我们的命名规范,名字叫a——那么:

  1. b迭代产生的每个值都直接传递给a的调用者。
  2. 所有通过send方法发送到a的值都被直接传递给b. 如果发送的 值是None,则调用b的__next__()方法,否则调用b的send 方法。如果对b的方法调用产生StopIteration异常,a会继续 执行yield from后面的语句,而其他异常则会传播到a中,导 致a在执行yield from的时候抛出异常。
  3. 如果有除GeneratorExit以外的异常被throw到a中的话,该异常 会被直接throw到b中。如果b的throw方法抛出StopIteration, a会继续执行;其他异常则会导致a也抛出异常。
  4. 如果一个GeneratorExit异常被throw到a中,或者a的close 方法被调用了,并且b也有close方法的话,b的close方法也 会被调用。如果b的这个方法抛出了异常,则会导致a也抛出异常。 反之,如果b成功close掉了,a也会抛出异常,但是是特定的 GeneratorExit异常。
  5. a中yield from表达式的求值结果是b迭代结束时抛出的 StopIteration异常的第一个参数。
  6. b中的return <expr>语句实际上会抛出StopIteration(<expr>) 异常,所以b中return的值会成为a中yield from表达式的返回值。

为神马会有这么多要求?因为generator这种东西的行为在加入throw 方法之后变得非常复杂,特别是几个generator在一起的情况,需要 类似进程管理的元语对其进行操作。上面的所有要求都是为了统一 generator原本就复杂的行为,自然简单不下来啦。

我承认我一下没看明白PEP的作者到底想说什么,于是动手“重构” 一遍大概会有点帮助。

一个没用的例子

说没用是因为你大概不会真的想把程序写成这样,但是……反正能说明 问题就够了。

设想有这样一个generator函数:

def inner():coef = 1 total = 0 while True: try: input_val = yield total total = total + coef * input_val except SwitchSign: coef = -(coef) except BreakOut: return total 

这个函数生成的generator将从send方法接收到的值累加到局部 变量total中,并且在收到BreakOut异常时停止迭代;至于另外 一个SwitchSign异常应该不难理解,这里就不剧透了。

从代码上看,由inner()函数得到的generator通过send接收用于 运算的数据,同时通过throw方法接受外部代码的控制以执行不同 的代码分支,目前为止都很清晰。

接下来因为需求有变动,我们需要在inner()这段代码的前后分别加 入初始化和清理现场的代码。鉴于我认为“没坏的代码就不要动”,我 决定让inner()维持现状,然后再写一个outer(),把添加的代码放在 outer()里,并提供与inner()一样的操作接口。由于inner()利用了 generator的若干特性,所以outer()也必须做到这五件事情:

  1. outer()必须生成一个generator;
  2. 在每一步的迭代中,outer()要帮助inner()返回迭代值;
  3. 在每一步的迭代中,outer()要帮助inner()接收外部发送的数据;
  4. 在每一步的迭代中,outer()要处理inner()接收和抛出所有异常;
  5. 在outer()被close的时候,inner()也要被正确地close掉。

根据上面的要求,在只有yield的世界里,outer()可能是长这样的:

def outer1():print("Before inner(), I do this.") i_gen = inner() input_val = None ret_val = i_gen.send(input_val) while True: try: input_val = yield ret_val ret_val = i_gen.send(input_val) except StopIteration: break except Exception as err: try: ret_val = i_gen.throw(err) except StopIteration: break print("After inner(), I do that.") 

WTF,这段代码比inner()本身还要长,而且还没处理close操作。

现在我们来试试外星科技:

def outer2():print("Before inner(), I do this.") yield from inner() print("After inner(), I do that.") 

除了完全符合上面的要求外,这四行代码打印出来的时候还能省点纸。

我们可以在outer1()和outer2()上分别测试 数据 以及 异常 的传递,不难发现这两个generator的行为基本上是一致的。既然如此, 外星科技当然在大多数情况下是首选。

对generator和coroutine的疑问

从以前接触到Python下的coroutine就觉得它怪怪的,我能看清它们的 行为模式,但是并不明白为什么要使用这种模式,generator和 coroutine具有一样的对外接口,是generator造就了coroutine呢,还 是coroutine造就了generator?最让我百思不得其解的是,Python下 的coroutine将“消息传递”和“调度”这两种操作绑在一个yield 上——即便有了yield from,这个状况还是没变过——我看不出这样做 的必要性。如果一开始就从语法层面将这两种语义分开,并且为 generator和coroutine分别设计一套接口,coroutine的概念大概也会 容易理解一些。

完整代码

comments powered by Disqus

转载于:https://www.cnblogs.com/edwardsun/p/8329290.html

Python3中的yield from语法相关推荐

  1. python格式化字符串语法_详解Python3 中的字符串格式化语法

    一.旧式的字符串格式化 % 操作符 参考以下示例: >>> name = "Eric" >>> "Hello, %s." % ...

  2. python3语法错误-python3中的def函数语法错误

    我正在自学如何编写代码,目前正在做一个掷骰子的学习项目.目前我遇到了一个奇怪的问题.我的代码中的第四个'def',不管它实际上是什么(我尝试了几个单独工作的),它总是被标记为语法错误.以下是我所拥有的 ...

  3. 可惜Java中没有yield return

    项目中一个消息推送需求,推送的用户数几百万,用户清单很简单就是一个txt文件,是由hadoop计算出来的.格式大概如下: uid caller 123456 12345678901 789101 12 ...

  4. 实际上,Python 3.3中新的“ yield from”语法的主要用途是什么?

    本文翻译自:In practice, what are the main uses for the new "yield from" syntax in Python 3.3? I ...

  5. python3 yield 大文件_详解Python3中yield生成器的用法

    任何使用yield的函数都称之为生成器,如: def count(n): while n > 0: yield n #生成值:n n -= 1 另外一种说法:生成器就是一个返回迭代器的函数,与普 ...

  6. C#.Net工作笔记009---c#中Yield Return语法的作用和好处

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 看到项目里用到了,查了一下,这个的作用是: yield return会对当前执行的 保存,阻塞当前 ...

  7. 详解Python3中yield生成器的用法

    这篇文章主要介绍了详解Python3中yield生成器的用法,是Python入门学习中的基础知识,需要的朋友可以参考下 任何使用yield的函数都称之为生成器,如: def count(n):whil ...

  8. python3 yield和iter的区别_Python中的yield关键字

    Yield? 总的来说,yield关键字和return关键字有相似之处,但其工作机制却大相径庭.想要完整地理解yield的工作机制,我们首先需要明白什么是generator.而为了明白什么是gener ...

  9. Python中的yield生成器的简单介绍

    Python yield 使用浅析(整理自:廖 雪峰, 软件工程师, HP 2012 年 11 月 22 日 ) 初学 Python 的开发者经常会发现很多 Python 函数中用到了 yield 关 ...

最新文章

  1. pycharm中的requirements.txt文件—如何生成及导入
  2. 性能建议(这里只针对单机版redis持久化做性能建议)
  3. html调用父页面的函数,js调用父框架函数与弹窗调用父页面函数的方法
  4. NFS-heartbeat-drbd模拟NFS高可用
  5. D3(v5) in TypeScript 坐标轴之 饼状图生成
  6. tomcat7简单优化
  7. 【MISC-zip密码破解】AZPR==>ARCHPR4.5
  8. vs2008 sp1
  9. 4矩阵键盘c语言程序,4×4矩阵键盘数码管显示按键值程序
  10. java查找pdf关键字_java实现查找PDF关键字所在页码及其坐标
  11. 如果在遨游浏览器里设置Bing(必应)搜索为默认搜索
  12. 网络传输介质和常见的设备
  13. [笔记] APIO 2018 Day1
  14. 微电子电路——反相器延迟时间
  15. lol老是闪退到桌面_lol闪退到桌面怎么解决
  16. 回顾之前给公司的建议,及离开前想说的话
  17. python羊车门问题的蒙特卡洛解法
  18. MongoDB4.2.0安装包并配上安装教程
  19. 操作系统——进程管理(一文弄懂进程间的那些事)
  20. 苹果手机(ipone)点击元素,事件不执行

热门文章

  1. ASP.NET状态管理之十三(总结)
  2. 在ASP.NET 3.5中使用新的ListView控件(3)
  3. 【opencv 学习】使用tesseract-ocr机芯数字识别
  4. 漫步最优化十三——驻点
  5. leetcode —— 面试题17. 打印从1到最大的n位数
  6. leetcode - 392. 判断子序列
  7. MAC系统下解决Teamviewers检测出商业限时问题
  8. Math3中StatUtils类和MathArrays的使用(数组运算)
  9. Eigen--Array
  10. XML编程-DOM4J