Python 3中的yield from语法详解

发布时间 - 2026-01-10 22:37:10    点击率:

前言

最近在捣鼓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能带来一定的帮助,如果有疑问大家可以留言交流。


# yield  # from  # python3  # send  # python  # Python 深入理解yield  # 详解Python3中yield生成器的用法  # Python中的生成器和yield详细介绍  # python中的yield使用方法  # Python中Yield的基本用法  # Python yield使用方法示例  # Python中生成器和yield语句的用法详解  # 对python中return与yield的区别详解  # 彻底理解Python中的yield关键字  # Python 中 yeild 的用法详解  # 抛出  # 迭代  # 神马  # 的是  # 都是  # 则会  # 也会  # 这段  # 这样一个  # 这两种  # 重构  # 几个  # 让我  # 也有  # 会有  # 是因为  # 还没  # 放在  # 有个  # 我看 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel的.env文件有什么用_Laravel环境变量配置与管理详解  Laravel如何实现文件上传和存储?(本地与S3配置)  佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】  Windows Hello人脸识别突然无法使用  Python自然语言搜索引擎项目教程_倒排索引查询优化案例  如何快速搭建高效可靠的建站解决方案?  Win11怎么设置默认图片查看器_Windows11照片应用关联设置  米侠浏览器网页图片不显示怎么办 米侠图片加载修复  如何用花生壳三步快速搭建专属网站?  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  Laravel如何生成API文档?(Swagger/OpenAPI教程)  如何续费美橙建站之星域名及服务?  Linux系统命令中tree命令详解  如何在建站主机中优化服务器配置?  如何打造高效商业网站?建站目的决定转化率  微博html5版本怎么弄发超话_超话进入入口及发帖格式要求【教程】  EditPlus中的正则表达式实战(5)  千问怎样用提示词获取健康建议_千问健康类提示词注意事项【指南】  浅谈Javascript中的Label语句  Laravel怎么使用artisan命令缓存配置和视图  实例解析angularjs的filter过滤器  Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程  PHP正则匹配日期和时间(时间戳转换)的实例代码  Laravel全局作用域是什么_Laravel Eloquent Global Scopes应用指南  Win11怎么关闭透明效果_Windows11辅助功能视觉效果设置  中国移动官方网站首页入口 中国移动官网网页登录  在线ppt制作网站有哪些软件,如何把网页的内容做成ppt?  什么是JavaScript解构赋值_解构赋值有哪些实用技巧  微信小程序制作网站有哪些,微信小程序需要做网站吗?  标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?  Laravel怎么配置不同环境的数据库_Laravel本地测试与生产环境动态切换【方法】  Laravel集合Collection怎么用_Laravel集合常用函数详解  php静态变量怎么调试_php静态变量作用域调试技巧【解答】  Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】  Linux网络带宽限制_tc配置实践解析【教程】  网站建设要注意的标准 促进网站用户好感度!  悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤  北京网站制作的公司有哪些,北京白云观官方网站?  音乐网站服务器如何优化API响应速度?  北京企业网站设计制作公司,北京铁路集团官方网站?  高端云建站费用究竟需要多少预算?  网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?  如何在腾讯云服务器快速搭建个人网站?  Laravel安装步骤详细教程_Laravel环境搭建指南  Internet Explorer官网直接进入 IE浏览器在线体验版网址  Laravel如何自定义分页视图?(Pagination示例)  轻松掌握MySQL函数中的last_insert_id()  如何快速搭建二级域名独立网站?  JavaScript如何实现类型判断_typeof和instanceof有什么区别  公司网站制作需要多少钱,找人做公司网站需要多少钱?