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

yield和yield from的更多相关文章

  1. python yield 与 yield from转

    python yield 与 yield from转 https://blog.csdn.net/chenbin520/article/details/78111399?locationNum=7&a ...

  2. yield与yield from

    yield 通过yield返回的是一个生成器,yield既可以产出值又可以生成值,yield可以用next()来启动生成器,同时可以用send向生成器传递值:在初次启动生成器时,需调用next()或s ...

  3. 从yield 到yield from再到python协程

    yield 关键字 def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b yield 是在:PEP 255 -- Simple Generator ...

  4. 60、简述 yield和yield from关键字。

    1.可迭代对象与迭代器的区别 可迭代对象:指的是具备可迭代的能力,即enumerable.  在Python中指的是可以通过for-in 语句去逐个访问元素的一些对象,比如元组tuple,列表list ...

  5. python yield、yield from与协程

    从生成器到协程 协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值.生成器的调用方可以使用 .send(...)方法发送数据,发送的数据会成为yield表达式的值.因此,生成器可以作为协程使 ...

  6. python协程--yield和yield from

    字典为动词“to yield”给出了两个释义:产出和让步.对于 Python 生成器中的 yield 来说,这两个含义都成立.yield item 这行代码会产出一个值,提供给 next(...) 的 ...

  7. 简述 yield和yield from关键字

    1.可迭代对象与迭代器的区别 可迭代对象:指的是具备可迭代的能力,即enumerable.  在Python中指的是可以通过for-in 语句去逐个访问元素的一些对象,比如元组tuple,列表list ...

  8. python yield && scrapy yield

    title: python yield && scrapy yield date: 2020-03-17 16:00:00 categories: python tags: 语法 yi ...

  9. 从yield到yield from再到python协程

    yield 关键字 def fib(): a,b = 0,1 while 1: yield b a,b = b,a+b yield是在:PEP 255 -- Simple Generators 这个p ...

  10. python莫名其妙的yield, yield from, yield.send

    练了几行代码, 慢慢找感觉. TASK,多线程,异步,很多地方都用到的呢. #!/usr/bin/env python # -*- coding: utf-8 -*- import time from ...

随机推荐

  1. python装饰器精髓代码

    #!/usr/bin/env python #-*- coding:utf-8 -*- import time def foo(func): def inner(): print('fs...') f ...

  2. 「LOJ#10034」「一本通 2.1 例 2」图书管理 (map

    题目描述 图书管理是一件十分繁杂的工作,在一个图书馆中每天都会有许多新书加入.为了更方便的管理图书(以便于帮助想要借书的客人快速查找他们是否有他们所需要的书),我们需要设计一个图书查找系统. 该系统需 ...

  3. UNP总结 Chapter 11 名字与地址转换

    本章讲述在名字和数值地址间进行转换的函数:gethostbyname和gethostbyaddr在主机名字与IP地址间进行转换,getservbyname和getservbyport在服务器名字和端口 ...

  4. bzoj 2179 FFT快速傅立叶 —— FFT

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2179 默写板子,注释的是忘记的地方. 代码如下: #include<iostream& ...

  5. 【旧文章搬运】Windows中全局钩子DLL的加载过程

    原文发表于百度空间,2011-03-24========================================================================== 看雪上别人 ...

  6. 一、mysql简述

    该套讲义参考动力节点郭鑫老师的mysql视频整理所得 1.DBMS--数据库管理系统  Data Base Management System eg: mysql数据库管理系统 2.DB--数据库/仓 ...

  7. ubuntu下使用锐捷校园网

    前言           以下内容是个人学习之后的感悟,转载请注明出处~ 1.首先下载锐捷Linux版本,然后解压缩后,有个rjsupplicant.sh这个脚本文件,于是按照README做了,终端中 ...

  8. JS获取元素的offsetTop,offsetLeft等相关属性

    1. obj.clientWidth //获取元素的宽度 obj.clientHeight //元素的高度 obj.offsetLeft //元素相对于父元素的left obj.offsetTop / ...

  9. 1.6-1.8 HBase表的物理模型

    一.HBase 物理模型 1. 1.Table中的所有行都按照row key的字典序排列: 2.Table在行的方向上分割为多个Region: 3.Region按天小分割的,每个表开始只有一个regi ...

  10. sql server 2008 删除某数据库所有表

    /* ------sqlserver 2008 删除某数据库所有表-------- */ declare @tname varchar(8000) set @tname='' select @tnam ...