Python 3中的yield from语法详解


Posted in Python onJanuary 18, 2017

前言

最近在捣鼓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的概念大概也会 容易理解一些。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家学习或者使用python能带来一定的帮助,如果有疑问大家可以留言交流。

Python 相关文章推荐
python抓取京东价格分析京东商品价格走势
Jan 09 Python
python实现的二叉树定义与遍历算法实例
Jun 30 Python
浅谈Pandas 排序之后索引的问题
Jun 07 Python
对python字典元素的添加与修改方法详解
Jul 06 Python
在Python运行时动态查看进程内部信息的方法
Feb 22 Python
Python实现带下标索引的遍历操作示例
May 30 Python
树莓派安装OpenCV3完整过程的实现
Oct 10 Python
Python Django框架url反向解析实现动态生成对应的url链接示例
Oct 18 Python
Python aiohttp百万并发极限测试实例分析
Oct 26 Python
pandas的resample重采样的使用
Apr 24 Python
Python JSON常用编解码方法代码实例
Sep 05 Python
分享PyCharm最新激活码(真永久激活方法)不用每月找安装参数或最新激活码了
Dec 27 Python
Python中的字符串操作和编码Unicode详解
Jan 18 #Python
关于Python中异常(Exception)的汇总
Jan 18 #Python
python:socket传输大文件示例
Jan 18 #Python
详解使用pymysql在python中对mysql的增删改查操作(综合)
Jan 18 #Python
python实现下载整个ftp目录的方法
Jan 17 #Python
ansible作为python模块库使用的方法实例
Jan 17 #Python
python 基础教程之Map使用方法
Jan 17 #Python
You might like
php excel reader读取excel内容存入数据库实现代码
2012/12/06 PHP
探讨php define()函数及defined()函数使用详解
2013/06/09 PHP
thinkphp实现把数据库中的列的值存到下拉框中的方法
2017/01/20 PHP
php实现不通过扩展名准确判断文件类型的方法【finfo_file方法与二进制流】
2017/04/18 PHP
thinkPHP框架实现的短信接口验证码功能示例
2018/06/20 PHP
Maps Javascript
2007/01/22 Javascript
JavaScript异步编程Promise模式的6个特性
2014/04/03 Javascript
JavaScript怎么判断图片是否加载完成以便获取其尺寸
2014/05/08 Javascript
JavaScript正则表达式的分组匹配详解
2016/02/13 Javascript
理解javascript正则表达式
2016/03/08 Javascript
分享javascript、jquery实用代码段
2016/10/20 Javascript
nodejs Assert中equal(),strictEqual(),deepEqual(),strictDeepEqual()比较
2017/09/18 NodeJs
微信小程序页面跳转功能之从列表的item项跳转到下一个页面的方法
2017/11/27 Javascript
详解VUE2.X过滤器的使用方法
2018/01/11 Javascript
webpack4 SplitChunks实现代码分隔详解
2019/05/23 Javascript
js实现点击图片在屏幕中间弹出放大效果
2019/09/11 Javascript
Vue3项目打包后部署到服务器 请求不到后台接口解决方法
2020/02/06 Javascript
vue使用svg文件补充-svg放大缩小操作(使用d3.js)
2020/09/22 Javascript
python 中的int()函数怎么用
2017/10/17 Python
Python读取csv文件分隔符设置方法
2019/01/14 Python
查看python安装路径及pip安装的包列表及路径
2019/04/03 Python
利用selenium爬虫抓取数据的基础教程
2019/06/10 Python
python 执行终端/控制台命令的例子
2019/07/12 Python
matplotlib命令与格式之tick坐标轴日期格式(设置日期主副刻度)
2019/08/06 Python
解决Pytorch 训练与测试时爆显存(out of memory)的问题
2019/08/20 Python
python主线程与子线程的结束顺序实例解析
2019/12/17 Python
python、PyTorch图像读取与numpy转换实例
2020/01/13 Python
python自动化办公操作PPT的实现
2021/02/05 Python
环保倡议书
2014/04/14 职场文书
公务员学习习总书记“三严三实”思想汇报
2014/09/19 职场文书
放弃遗产继承公证书
2015/01/26 职场文书
青年志愿者服务活动总结
2015/05/06 职场文书
高一语文教学反思
2016/02/16 职场文书
2016年优秀少先队员事迹材料
2016/02/26 职场文书
《别在吃苦的年纪选择安逸》读后感3篇
2019/11/30 职场文书
基于Python实现的购物商城管理系统
2021/04/27 Python