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——那么:

b迭代产生的每个值都直接传递给a的调用者。 
所有通过send方法发送到a的值都被直接传递给b. 如果发送的 值是None,则调用b的__next__()方法,否则调用b的send 方法。如果对b的方法调用产生StopIteration异常,a会继续 执行yield from后面的语句,而其他异常则会传播到a中,导 致a在执行yield from的时候抛出异常。 
如果有除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中yield from表达式的求值结果是b迭代结束时抛出的  StopIteration异常的第一个参数。 
b中的return 语句实际上会抛出StopIteration( ) 异常,所以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()也必须做到这五件事情:

outer()必须生成一个generator; 
在每一步的迭代中,outer()要帮助inner()返回迭代值; 
在每一步的迭代中,outer()要帮助inner()接收外部发送的数据; 
在每一步的迭代中,outer()要处理inner()接收和抛出所有异常; 
在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的概念大概也会 容易理解一些。

最新文章

  1. Mac下设置Android源代码编译环境
  2. git本地提交到远程仓库命令
  3. js/jQuery判断浏览器名称、内核版本、浏览器壳
  4. 解决 01-Jul-2016 10:49:05.875 WARNING [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc The web application [ROOT] registered the JDBC driver [com.mysql.jdbc.D
  5. G-nav-04
  6. IE浏览器模式设置
  7. ios事件传递
  8. SecureCRT注册机使用方法
  9. OC使用inline替代宏
  10. linux笔试
  11. linux驱动开发之GCC问题
  12. smart pointer (auto_ptr)
  13. C++中的智能指针(auto_ptr)
  14. .bashrc:16: command not found: shopt配置环境变量时出错
  15. [LeetCode] Split Array with Equal Sum 分割数组成和相同的子数组
  16. 基于Centos体验自然语言处理 by Python SDK
  17. Nginx的安装和使用(Linux)
  18. 实用JS代码
  19. zk可视化工具
  20. HDU 5316——Magician——————【线段树区间合并区间最值】

热门文章

  1. luogu 3812 【模板】 线性基
  2. 万径人踪灭(FFT+manacher)
  3. Tyvj1052(树形DP)
  4. hibernate VS mybatis
  5. mysql 快捷键
  6. 共相式GIS
  7. JAG Practice Contest for ACM-ICPC Asia Regional 2016B题【BFS】
  8. C++中各种简写及全称的库
  9. hadoop HA集群搭建(亲测)
  10. go系列(1)- linux下安装go环境