源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

Python 3中的yield from语法详解

  • 时间:2022-12-19 20:09 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:Python 3中的yield from语法详解
[b]前言[/b] 最近在捣鼓[url=http://autobahn.ws/]Autobahn[/url],它有给出个例子是基于asyncio 的,想着说放到[url=http://doc.pypy.org/en/latest/release-pypy3-2.1.0-beta1.html]pypy3[/url]上跑跑看竟然就……失败了。 [code]pip install asyncio[/code]直接报invalid syntax,粗看还以为2to3处理的时 候有问题——这不能怪我,好~多package都是用2写了然后转成3的——结果发 现asyncio本来就只支持3.3+的版本,才又回头看代码,赫然发现一句 [code]yield from[/code];[code]yield[/code]我知道,但是[code]yield from[/code]是神马? [b]PEP-380[/b] 好吧这个标题是我google出来的,[code]yield from[/code]的前世今生都在 这个PEP里面,总之大意是原本的[code]yield[/code]语句只能将CPU控制权 还给直接调用者,当你想要将一个generator或者coroutine里带有 yield语句的逻辑重构到另一个generator(原文是subgenerator) 里的时候,会非常麻烦,因为外面的generator要负责为里面的 generator做消息传递;所以某人有个想法是让python把消息传递 封装起来,使其对程序猿透明,于是就有了[code]yield from[/code]。 PEP-380规定了[code]yield from[/code]的语义,或者说嵌套的generator应该 有的行为模式。 假设A函数中有这样一个语句
yield from B()
[code]B()[/code]返回的是一个可迭代(iterable)的对象b,那么A()会返回一个 generator——照我们的命名规范,名字叫a——那么: [list=1] [*]b迭代产生的每个值都直接传递给a的调用者。[/*] [*]所有通过send方法发送到a的值都被直接传递给b. 如果发送的 值是None,则调用b的[code]__next__()[/code]方法,否则调用b的send 方法。如果对b的方法调用产生StopIteration异常,a会继续 执行[code]yield from[/code]后面的语句,而其他异常则会传播到a中,导 致a在执行[code]yield from[/code]的时候抛出异常。[/*] [*]如果有除GeneratorExit以外的异常被throw到a中的话,该异常 会被直接throw到b中。如果b的throw方法抛出StopIteration, a会继续执行;其他异常则会导致a也抛出异常。[/*] [*]如果一个GeneratorExit异常被throw到a中,或者a的close 方法被调用了,并且b也有close方法的话,b的close方法也 会被调用。如果b的这个方法抛出了异常,则会导致a也抛出异常。 反之,如果b成功close掉了,a也会抛出异常,但是是特定的  GeneratorExit异常。[/*] [*]a中[code]yield from[/code]表达式的求值结果是b迭代结束时抛出的  StopIteration异常的第一个参数。[/*] [*]b中的[code]return <expr>[/code]语句实际上会抛出[code]StopIteration(<expr>) [/code]异常,所以b中return的值会成为a中[code]yield from[/code]表达式的返回值。 [/*] [/list] 为神马会有这么多要求?因为generator这种东西的行为在加入throw 方法之后变得非常复杂,特别是几个generator在一起的情况,需要 类似进程管理的元语对其进行操作。上面的所有要求都是为了统一 generator原本就复杂的行为,自然简单不下来啦。 我承认我一下没看明白PEP的作者到底想说什么,于是动手“重构” 一遍大概会有点帮助。 [b]一个没用的例子[/b] 说没用是因为你大概不会真的想把程序写成这样,但是……反正能说明 问题就够了。 设想有这样一个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异常应该不难理解,这里就不剧透了。 从代码上看,由[code]inner()[/code]函数得到的generator通过send接收用于 运算的数据,同时通过throw方法接受外部代码的控制以执行不同 的代码分支,目前为止都很清晰。 接下来因为需求有变动,我们需要在[code]inner()[/code]这段代码的前后分别加 入初始化和清理现场的代码。鉴于我认为“没坏的代码就不要动”,我 决定让[code]inner()[/code]维持现状,然后再写一个[code]outer()[/code] ,把添加的代码放在 [code]outer()[/code]里,并提供与[code]inner()[/code]一样的操作接口。由于[code]inner()[/code]利用了 generator的若干特性,所以[code]outer()[/code]也必须做到这五件事情: [list=1] [*][code]outer()[/code]必须生成一个generator;[/*] [*]在每一步的迭代中,[code]outer()[/code]要帮助[code]inner()[/code]返回迭代值;[/*] [*]在每一步的迭代中,[code]outer()[/code]要帮助[code]inner()[/code]接收外部发送的数据;[/*] [*]在每一步的迭代中,[code]outer()[/code]要处理[code]inner()[/code]接收和抛出所有异常;[/*] [*]在[code]outer()[/code]被close的时候,[code]inner()[/code]也要被正确地close掉。 [/*] [/list] 根据上面的要求,在只有yield的世界里,[code]outer()[/code]可能是长这样的:
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,这段代码比[code]inner()[/code]本身还要长,而且还没处理close操作。 现在我们来试试外星科技:
def outer2():
 print("Before inner(), I do this.")
 yield from inner()
 print("After inner(), I do that.")
除了完全符合上面的要求外,这四行代码打印出来的时候还能省点纸。 我们可以在[code]outer1()[/code]和[code]outer2()[/code]上分别测试 数据 以及 异常 的传递,不难发现这两个generator的行为基本上是一致的。既然如此, 外星科技当然在大多数情况下是首选。 [b]对generator和coroutine的疑问[/b] 从以前接触到Python下的coroutine就觉得它怪怪的,我能看清它们的 行为模式,但是并不明白为什么要使用这种模式,generator和 coroutine具有一样的对外接口,是generator造就了coroutine呢,还 是coroutine造就了generator?最让我百思不得其解的是,Python下 的coroutine将“消息传递”和“调度”这两种操作绑在一个yield 上——即便有了[code]yield from[/code],这个状况还是没变过——我看不出这样做 的必要性。如果一开始就从语法层面将这两种语义分开,并且为 generator和coroutine分别设计一套接口,coroutine的概念大概也会 容易理解一些。 [b]总结[/b] 以上就是这篇文章的全部内容了,希望本文的内容对大家学习或者使用python能带来一定的帮助,如果有疑问大家可以留言交流。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部