声明:本文将详细讲解python协程的实现机理,为了彻底的弄明白它到底是怎么一回事,鉴于篇幅较长,将彻底从最简单的yield说起从最简单的生成器开始说起,因为很多看到这样一句话的时候很懵,即“yield也是一种简单的协程”,这到底是为什么呢?本次系列文章“python协程系列文章”将从最简单的生成器、yield、yield from说起,然后详细讲解asyncio的实现方式。本文主要讲解什么是yield from的详细实现、它有什么作用、实现动机是什么,将一层一层揭开它的面纱。

一、yield from 的简单实现

从前面的系列文章中,我们了解到,yield是每次“惰性返回”一个值,其实从名字中就能看出,yield from 是yield的升级改进版本,如果将yield理解成“返回”,那么yield from就是“从什么(生成器)里面返回”,这就构成了yield from的一般语法,即

yield from generator

这样的形式。我们通过一个简单例子来看:

def generator2():yield 'a'yield 'b'yield 'c'yield from generator1() #yield from iterable本质上等于 for item in iterable: yield item的缩写版yield from [11,22,33,44]yield from (12,23,34)yield from range(3)for i in generator2():print(i,end=' , ')
'''运行的结果为:
a , b , c , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 11 , 22 , 33 , 44 , 12 , 23 , 34 , 0 , 1 , 2 ,
'''

总结:
从上面的代码可以看书,yield from 后面可以跟的可以是“ 生成器 、元组、 列表、range()函数产生的序列等可迭代对象”

简单地说,yield from  generator 。实际上就是返回另外一个生成器。而yield只是返回一个元素。从这个层面来说,有下面的等价关系:yield from iterable本质上等于 for item in iterable: yield item 。

二、yield from的高级应用

当然,yield from既然称之为yield的升级改进版,如果仅仅是上面的那一点作用,显然这是不够的,因为那仅仅简化了一两句代码的事情。系列文章的上一篇讲解了yield所存在的缺点,参见上一篇文章:

https://blog.csdn.net/qq_27825451/article/details/85234610

yield from正式针对哪些不足来加以改进的。

1、针对yield无法获取生成器return的返回值

我们都知道,在使用yield生成器的时候,如果使用for语句去迭代生成器,则不会显式的出发StopIteration异常,而是自动捕获StopIteration异常,所以如果遇到return,只是会终止迭代,而不会触发异常,故而也就没办法获取return的值。如下:

def my_generator():for i in range(5):if i==2:return '我被迫中断了'else:yield idef main(generator):try:for i in generator:  #不会显式触发异常,故而无法获取到return的值print(i)except StopIteration as exc:print(exc.value)g=my_generator()  #调用
main(g)
'''运行结果为:
0
1
'''

从上面的例子可以看出,for迭代语句不会显式触发异常,故而无法获取到return的值,迭代到2的时候遇到return语句,隐式的触发了StopIteration异常,就终止迭代了,但是在程序中不会显示出来。

但是如果我是使用next(g)一次一次迭代,则会显式触发异常,但要获取return的返回值,我需要如下操作:

def my_generator():for i in range(5):if i==2:return '我被迫中断了'else:yield idef main(generator):try:print(next(generator))   #每次迭代一个值,则会显式出发StopIterationprint(next(generator))print(next(generator))print(next(generator))print(next(generator))except StopIteration as exc:print(exc.value)     #获取返回的值g=my_generator()
main(g)
'''运行结果为:
0
1
我被迫中断了
'''

现在我们使用yield from来完成上面的同样的功能:

def my_generator():for i in range(5):if i==2:return '我被迫中断了'else:yield idef wrap_my_generator(generator):  #定义一个包装“生成器”的生成器,它的本质还是生成器result=yield from generator    #自动触发StopIteration异常,并且将return的返回值赋值给yield from表达式的结果,即resultprint(result)def main(generator):for j in generator:print(j)g=my_generator()
wrap_g=wrap_my_generator(g)
main(wrap_g)  #调用
'''运行结果为:
0
1
我被迫中断了
'''

从上面的比较可以看出,yield from具有以下几个特点:

(1)上面的my_generator是原始的生成器,main是调用方,使用yield的时候,只涉及到这两个函数,即“调用方”与“生成器(协程函数)”是直接进行交互的,不涉及其他方法,即“调用方——>生成器函数(协程函数)”

(2)在使用yield from的时候,多了一个对原始my_generator的包装函数,然后调用方是通过这个包装函数(后面会讲到它专有的名词)来与生成器进行交互的,即“调用方——>生成器包装函数——>生成器函数(协程函数)”;

(3)yield from iteration结构会在内部自动捕获 iteration生成器的StopIteration 异常。这种处理方式与 for 循环处理 StopIteration 异常的方式一样。而且对 yield from 结构来说,解释器不仅会捕获 StopIteration 异常,还会把return返回的值或者是StopIteration的value 属性的值变成 yield from 表达式的值,即上面的result。

2、yield from所实现的数据传输通道

前面总结的几个特点里面已经介绍了yield和yield from的数据交互方式,yield涉及到“调用方与生成器两者”的交互,生成器通过next()的调用将值返回给调用者,而调用者通过send()方法向生成器发送数据;

但是yield还有一个第三者函数,下面将先从相关的概念说起。

在PEP 380 使用了一些yield from使用的专门术语:

委派生成器:包含 yield from <iterable> 表达式的生成器函数;即上面的wrap_my_generator生成器函数

子生成器:从 yield from 表达式中 <iterable> 部分获取的生成器;即上面的my_generator生成器函数

调用方:调用委派生成器的客户端代码;即上面的main生成器函数

下图是这三者之间的交互关系(摘自博客园):

委派生成器在 yield from 表达式处暂停时,调用方可以直接把数据发给子生成器,子生成器再把产出的值发给调用方。子生成器返回之后,解释器会抛出StopIteration 异常,并把返回值附加到异常对象上,此时委派生成器会恢复。

总结:

(1)yield from主要设计用来向子生成器委派操作任务,但yield from可以向任意的可迭代对象委派操作;

(2)委派生成器(group)相当于管道,所以可以把任意数量的委派生成器连接在一起---一个委派生成器使用yield from 调用一个子生成器,而那个子生成器本身也是委派生成器,使用yield from调用另一个生成器。

3、针对yield存在的第二个缺点

首先看一下他要表述的意思是什么?它的局限性在于只能向它的直接调用者每次yield一个值。这意味着那些包含yield的代码不能像其他代码那样被分离出来放到一个单独的函数中。这也正是yield from要解决的。具体参见上文:

https://blog.csdn.net/qq_27825451/article/details/85234610

这句话确实难以理解,但是他要表达的意思实际上是:因为生成器从定义上来看,就像是一个普通的函数,那么既然作为普通函数,就应该可以反反复复调用都没问题的,但是生成器却并不行。那为什么yield from可以解决这样的问题呢,主要是因为yield from后面可以跟任意一个生成器,即yield from可以将任意的任务为派给任意生成器函数,从而避免了子生成器直接向调用者返回单个值的情况。

(备注:这一块感觉自己也并不是理解的特别清楚,哪位大神看见,希望可以不吝赐教)

三、yield from的用法示例

其实yield from最重要的作用就是提供了一个“数据传输的管道”,下面通过一个简单的例子加以说明为什么是管道:

def average():total = 0.0  #数字的总和count = 0    #数字的个数avg = None   #平均值while True:num = yield avgtotal += numcount += 1avg = total/countdef wrap_average(generator):yield from generator#定义一个函数,通过这个函数向average函数发送数值
def main(wrap):print(next(wrap))  #启动生成器print(wrap.send(10))  # 10print(wrap.send(20))  # 15print(wrap.send(30))  # 20print(wrap.send(40))  # 25g = average()
wrap=wrap_average(g)
main(wrap)'''运行结果为:
None
10.0
15.0
20.0
25.0
'''

从上面我们可以发现,调用方发送的数据是发给wrap_average的,怎么依然到了生成器函数average里面呢?这就是“数据传输管道的作用”。即主函数调用方main把各个value传给grouper ,而这个传入的值最终到达averager函数中; grouper并不知道传入的是什么值,因为从上面的代码看出,wrap_average里面完全没有处理这个值的任何代码!

现在是不是对“数据传输管道”更加理解了呢?

python协程系列(三)——yield from原理详解相关推荐

  1. python函数定义及调用-python函数声明和调用定义及原理详解

    这篇文章主要介绍了python函数声明和调用定义及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 函数是指代码片段,可以重复调用,比如我们前 ...

  2. python函数声明和调用定义及原理详解

    这篇文章主要介绍了python函数声明和调用定义及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 函数是指代码片段,可以重复调用,比如我们前 ...

  3. (转)python协程2:yield from 从入门到精通

    原文:http://blog.gusibi.com/post/python-coroutine-yield-from/ https://mp.weixin.qq.com/s?__biz=MzAwNjI ...

  4. Python协程:从yield/send到async/await

    这个文章理好了脉落. http://python.jobbole.com/86069/ 我练 习了一番,感受好了很多... Python由于众所周知的GIL的原因,导致其线程无法发挥多核的并行计算能力 ...

  5. VMware Workstation网络连接的三种方式原理详解 与 配置过程图解

    VMware workstations为我们提供了三种网络工作模式,它们分别是:Bridged(桥接模式).NAT(网络地址转换模式).Host-Only(仅主机模式). 打开vmware虚拟机,我们 ...

  6. TCP三次握手原理详解

    TCP/IP协议不是TCP和IP这两个协议的合称,而是指因特网整个TCP/IP协议族. 从协议分层模型方面来讲,TCP/IP由四个层次组成:网络接口层.网络层.传输层.应用层. TCP协议:即传输控制 ...

  7. python语言原理_从零开始学Python自然语言处理(十三)——CBOW原理详解

    在之前的连载中我们用代码实现了word2vec,但并没有详细讲解CBOW的原理,本文手把手带你走入CBOW的算法原理. word2vec是一种将word转为向量的方法,其包含两种算法,分别是skip- ...

  8. python的基本原理_Python函数基本使用原理详解

    1.什么是函数 函数就相当于具备某一功能的工具 函数的使用必须遵循一个原则: 先定义 后调用 2.为何要用函数 1.组织结构不清晰,可读性差 2.代码冗余 3.可维护性.扩展性差 3.如何用函数 1. ...

  9. 循环神经网络RNN、LSTM、GRU原理详解

    一.写在前面 这部分内容应该算是近几年发展中最基础的部分了,但是发现自己忘得差不多了,很多细节记得不是很清楚了,故写这篇博客,也希望能够用更简单清晰的思路来把这部分内容说清楚,以此能够帮助更多的朋友, ...

最新文章

  1. html 图片时钟,教你五步制作精美的HTML时钟
  2. python输出变量代码_Python中变量的输入输出实例代码详解
  3. Flagger on ASM——基于Mixerless Telemetry实现渐进式灰度发布系列 1 遥测数据
  4. java 日期处理工具类_Java日期处理工具类DateUtils详解
  5. GCD中的队列与任务
  6. c# mvc5 view 多层_三、 添加视图View(ASP.NET MVC5 系列)
  7. ubuntu执行configure配置代码出现unable to guess system type报错
  8. JAVA转为wasm/JavaScript,可以考虑CheerpJ
  9. Elasticsearch 磁盘使用率超过警戒水位线,怎么办?
  10. Hadoop,HBASE启动命令
  11. centos7离线安装wget
  12. php实现微信小程序消息通知
  13. Linux下图片 jpg、png、gif 与 eps 格式的相互转换
  14. python刷博客点击量
  15. 艾睿电子Arrow EDI ORDERS订单解读
  16. 面试官:谈谈对JS闭包的理解及常见应用场景(闭包的作用)
  17. iPhone SE2外观酷似iPhone 8,香吗?
  18. 正则表达式常用语法速查+一个简单使用案例
  19. 病原微生物高通量测序:第二节 应用场景
  20. android 按钮加上蒙层,Android PopupWindow增加半透明蒙层

热门文章

  1. 基于51单片机的简易抢答器设计
  2. Axios获取后端返回的二进制数据流并下载下来
  3. 言简意赅介绍和对比3D结构光与TOF
  4. Qt国际化翻译(中英切换)步骤:可子界面翻译
  5. 一定用好自己的应届毕业生身份
  6. 大学大数据、人工智能相关专业到底有没有前途?
  7. 如何使用JavaScript替换数组中的项?
  8. Linux系统校准时间同步时间
  9. 视频怎样做二维码?视频转成二维码的完整教程
  10. 认识Python的PPT