原文:http://blog.gusibi.com/post/python-coroutine-yield-from/

https://mp.weixin.qq.com/s?__biz=MzAwNjI5MjAzNw==&mid=2655751983&idx=1&sn=e4c093c6e5d6e4e8281d76db7c67eb23---------------python协程1:yield的使用

https://juejin.im/post/5b3af9fb51882507d4487144-----深入理解Python的yield from语法

http://www.cnblogs.com/cdma/p/6839640.html---------python yield 和 yield from用法总结

  • 生成器作为协程使用时的行为和状态
  • 使用装饰器预激协程
  • 调用方如何使用生成器对象的 .throw(…) 和 .close() 方法控制协程

这一篇将介绍:

  • 协程终止时如何返回值
  • yield新句法的用途和语义

HUGOMORE42

1 让协程返回值

先看一个例子: 这段代码会返回最终均值的结果,每次激活协程时不会产出移动平均值,而是最后一次返回。

#! -*- coding: utf-8 -*-from collections import namedtupleResult = namedtuple('Result', 'count average') def averager(): total = 0.0 count = 0 average = None while True: term = yield if term is None: break # 为了返回值,协程必须正常终止;这里是退出条件 total += term count += 1 average = total/count # 返回一个namedtuple,包含count和average两个字段。在python3.3前,如果生成器返回值,会报错 return Result(count, average) 

我们调用这段代码,结果如下

>>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(20) # 并没有返回值 >>> coro_avg.send(30) >>> coro_avg.send(40) >>> coro_avg.send(None) # 发送None终止循环,导致协程结束。生成器对象会抛出StopIteration异常。异常对象的value属性保存着返回值。 Traceback (most recent call last): ... StopIteration: Result(count=3, average=30) 

return 表达式的值会传给调用方,赋值给StopIteration 异常的一个属性。这样做虽然看着别扭,但为了保留生成器对象耗尽时抛出StopIteration异常的行为,也可以理解。

如果我们想获取协程的返回值,可以这么操作:

>>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(20) # 并没有返回值 >>> coro_avg.send(30) >>> coro_avg.send(40) >>> try: ... coro_avg.send(None) ... except StopIteration as exc: ... result = exc.value ... >>> result Result(count=3, average=30) 

看到这我们会说,这是什么鬼,为什么获取返回值要绕这么一大圈,就没有简单的方法吗?

有的,那就是 yield from

yield from 结果会在内部自动捕获StopIteration 异常。这种处理方式与 for 循环处理StopIteration异常的方式一样。 对于yield from 结构来说,解释器不仅会捕获StopIteration异常,还会把value属性的值变成yield from 表达式的值。

在函数外部不能使用yield from(yield也不行)。

既然我们提到了 yield from 那yield from 是什么呢?

2 yield from

yield from 是 Python3.3 后新加的语言结构。和其他语言的await关键字类似,它表示:*在生成器 gen 中使用 yield from subgen()时,subgen 会获得控制权,把产出的值传个gen的调用方,即调用方可以直接控制subgen。于此同时,gen会阻塞,等待subgen终止。

yield from 可用于简化for循环中的yield表达式。

例如:

def gen():for c in 'AB': yield c for i in range(1, 3): yield i list(gen()) ['A', 'B', '1', '2'] 

可以改写为:

def gen():yield from 'AB' yield from range(1, 3) list(gen()) ['A', 'B', '1', '2'] 

下面来看一个复杂点的例子:(来自Python cookbook 3 ,github源码地址 https://github.com/dabeaz/python-cookbook/blob/master/src/4/how_to_flatten_a_nested_sequence/example.py)

# Example of flattening a nested sequence using subgeneratorsfrom collections import Iterabledef flatten(items, ignore_types=(str, bytes)): for x in items: if isinstance(x, Iterable) and not isinstance(x, ignore_types): yield from flatten(x) # 这里递归调用,如果x是可迭代对象,继续分解 else: yield x items = [1, 2, [3, 4, [5, 6], 7], 8] # Produces 1 2 3 4 5 6 7 8 for x in flatten(items): print(x) items = ['Dave', 'Paula', ['Thomas', 'Lewis']] for x in flatten(items): print(x) 

yield from x 表达式对x对象做的第一件事是,调用 iter(x),获取迭代器。所以要求x是可迭代对象。

PEP380 的标题是 ”syntax for delegating to subgenerator“(把指责委托给子生成器的句法)。由此我们可以知道,yield from是可以实现嵌套生成器的使用。

yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,使两者可以直接发送和产出值,还可以直接传入异常,而不用在中间的协程添加异常处理的代码。

yield from 包含几个概念:

  • 委派生成器

包含yield from 表达式的生成器函数

  • 子生成器

从yield from 部分获取的生成器。

  • 调用方

调用委派生成器的客户端(调用方)代码

这个示意图是 对yield from 的调用过程

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

这个图来自于Paul Sokolovsky 的 How Python 3.3 “yield from” construct works

下边这个例子是对yield from 的一个应用:

 #! -*- coding: utf-8 -*-from collections import namedtupleResult = namedtuple('Result', 'count average') # 子生成器 # 这个例子和上边示例中的 averager 协程一样,只不过这里是作为字生成器使用 def averager(): total = 0.0 count = 0 average = None while True: # main 函数发送数据到这里 term = yield if term is None: # 终止条件 break total += term count += 1 average = total/count return Result(count, average) # 返回的Result 会成为grouper函数中yield from表达式的值 # 委派生成器 def grouper(results, key): # 这个循环每次都会新建一个averager 实例,每个实例都是作为协程使用的生成器对象 while True: # grouper 发送的每个值都会经由yield from 处理,通过管道传给averager 实例。grouper会在yield from表达式处暂停,等待averager实例处理客户端发来的值。averager实例运行完毕后,返回的值绑定到results[key] 上。while 循环会不断创建averager实例,处理更多的值。 results[key] = yield from averager() # 调用方 def main(data): results = {} for key, values in data.items(): # group 是调用grouper函数得到的生成器对象,传给grouper 函数的第一个参数是results,用于收集结果;第二个是某个键 group = grouper(results, key) next(group) for value in values: # 把各个value传给grouper 传入的值最终到达averager函数中; # grouper并不知道传入的是什么,同时grouper实例在yield from处暂停 group.send(value) # 把None传入groupper,传入的值最终到达averager函数中,导致当前实例终止。然后继续创建下一个实例。 # 如果没有group.send(None),那么averager子生成器永远不会终止,委派生成器也永远不会在此激活,也就不会为result[key]赋值 group.send(None) report(results) # 输出报告 def report(results): for key, result in sorted(results.items()): group, unit = key.split(';') print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit)) data = { 'girls;kg':[40, 41, 42, 43, 44, 54], 'girls;m': [1.5, 1.6, 1.8, 1.5, 1.45, 1.6], 'boys;kg':[50, 51, 62, 53, 54, 54], 'boys;m': [1.6, 1.8, 1.8, 1.7, 1.55, 1.6], } if __name__ == '__main__': main(data) 

这段代码从一个字典中读取男生和女生的身高和体重。然后把数据传给之前定义的 averager 协程,最后生成一个报告。

执行结果为

6 boys  averaging 54.00kg
6 boys  averaging 1.68m
6 girls averaging 44.00kg 6 girls averaging 1.58m 

这断代码展示了yield from 结构最简单的用法。委派生成器相当于管道,所以可以把任意数量的委派生成器连接在一起—一个委派生成器使用yield from 调用一个子生成器,而那个子生成器本身也是委派生成器,使用yield from调用另一个生成器。最终以一个只是用yield表达式的生成器(或者任意可迭代对象)结束。

3 yield from 的意义

PEP380 分6点说明了yield from 的行为。

  • 子生成器产出的值都直接传给委派生成器的调用方(客户端代码)
  • 使用send() 方法发给委派生成器的值都直接传给子生成器。如果发送的值是None,那么会调用子生成器的 next()方法。如果发送的值不是None,那么会调用子生成器的send()方法。如果调用的方法抛出StopIteration异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。
  • 生成器退出时,生成器(或子生成器)中的return expr 表达式会触发 StopIteration(expr) 异常抛出。
  • yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数。
  • 传入委派生成器的异常,除了 GeneratorExit 之外都传给子生成器的throw()方法。如果调用throw()方法时抛出 StopIteration 异常,委派生成器恢复运行。StopIteration之外的异常会向上冒泡。传给委派生成器。
  • 如果把 GeneratorExit 异常传入委派生成器,或者在委派生成器上调用close() 方法,那么在子生成器上调用close() 方法,如果他有的话。如果调用close() 方法导致异常抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出 GeneratorExit 异常。

yield from的具体语义很难理解,不过我们可以看下Greg Ewing 的伪代码,通过伪代码分析一下:

RESULT = yield from EXPR# is semantically equivalent to
# EXPR 可以是任何可迭代对象,因为获取迭代器_i 使用的是iter()函数。
_i = iter(EXPR)
try: _y = next(_i) # 2 预激字生成器,结果保存在_y 中,作为第一个产出的值 except StopIteration as _e: # 3 如果调用的方法抛出StopIteration异常,获取异常对象的value属性,赋值给_r _r = _e.value else: while 1: # 4 运行这个循环时,委派生成器会阻塞,只能作为调用方和子生成器直接的通道 try: _s = yield _y # 5 产出子生成器当前产出的元素;等待调用方发送_s中保存的值。 except GeneratorExit as _e: # 6 这一部分是用于关闭委派生成器和子生成器,因为子生成器可以是任意可迭代对象,所以可能没有close() 方法。 try: _m = _i.close except AttributeError: pass else: _m() # 如果调用close() 方法导致异常抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出 GeneratorExit 异常。 raise _e except BaseException as _e: # 7 这一部分处理调用方通过.throw() 方法传入的异常。如果子生成器是迭代器,没有throw()方法,这种情况会导致委派生成器抛出异常 _x = sys.exc_info() try: # 传入委派生成器的异常,除了 GeneratorExit 之外都传给子生成器的throw()方法。 _m = _i.throw except AttributeError: # 子生成器一迭代器,没有throw()方法, 调用throw()方法时抛出AttributeError异常传给委派生成器 raise _e else: # 8 try: _y = _m(*_x) except StopIteration as _e: # 如果调用throw()方法时抛出 StopIteration 异常,委派生成器恢复运行。 # StopIteration之外的异常会向上冒泡。传给委派生成器。 _r = _e.value break else: # 9 如果产出值时没有异常 try: # 10 尝试让子生成器向前执行 if _s is None: # 11. 如果发送的值是None,那么会调用子生成器的 __next__()方法。 _y = next(_i) else: # 11. 如果发送的值不是None,那么会调用子生成器的send()方法。 _y = _i.send(_s) except StopIteration as _e: # 12 # 2. 如果调用的方法抛出StopIteration异常,获取异常对象的value属性,赋值给_r, 退出循环,委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。 _r = _e.value break RESULT = _r #13 返回的结果是 _r 即整个yield from表达式的值 

上段代码变量说明:

  • _i 迭代器(子生成器)
  • _y 产出的值 (子生成器产出的值)
  • _r 结果 (最终的结果 即整个yield from表达式的值)
  • _s 发送的值 (调用方发给委派生成器的值,这个只会传给子生成器)
  • _e 异常 (异常对象)

我们可以看到在代码的第一个 try 部分 使用 _y = next(_i) 预激了子生成器。这可以看出,上一篇我们使用的用于自动预激的装饰器与yield from 语句不兼容。

除了这段伪代码之外,PEP380 还有个说明:

In a generator, the statementreturn valueis semantically equivalent toraise StopIteration(value)except that, as currently, the exception cannot be caught by except clauses within the returning generator. 

这也就是为什么 yield from 可以使用return 来返回值而 yield 只能使用 try … except StopIteration … 来捕获异常的value 值。

>>> try:
...     coro_avg.send(None)
... except StopIteration as exc: ... result = exc.value ... >>> result 

到这里,我们已经了解了 yield from 的具体细节。下一篇,会分析一个使用协程的经典案例: 仿真编程。这个案例说明了如何使用协程在单线程中管理并发活动。

转载于:https://www.cnblogs.com/liujiacai/p/10093397.html

(转)python协程2:yield from 从入门到精通相关推荐

  1. python协程--yield和yield from

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

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

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

  3. 理解Python的协程机制-Yield

    根据PEP-0342 Coroutines via Enhanced Generators,原来仅仅用于生成器的yield关键字被扩展,成为Python协程实现的一部分.而之所以使用协程,主要是出于性 ...

  4. python协程系列(三)——yield from原理详解

    声明:本文将详细讲解python协程的实现机理,为了彻底的弄明白它到底是怎么一回事,鉴于篇幅较长,将彻底从最简单的yield说起从最简单的生成器开始说起,因为很多看到这样一句话的时候很懵,即" ...

  5. python yield 协程_用yield实现python协程

    刚刚介绍了pythonyield关键字,趁热打铁,现在来了解一下yield实现协程. 引用官方的说法: 与线程相比,协程更轻量.一个python线程大概占用8M内存,而一个协程只占用1KB不到内存.协 ...

  6. 学习笔记(35):Python网络编程并发编程-协程(yield,greenlet,gevent模块)

    立即学习:https://edu.csdn.net/course/play/24458/296457?utm_source=blogtoedu 协程(yield,greenlet,gevent) 1. ...

  7. python 协程_Python 协程与 Go 协程的区别(一)

    ? "Python猫" ,一个值得加星标的公众号 花下猫语:年关将近,不知各位过得怎样?我最近有些忙,收获也挺多,以后有机会分享下.吃饭时间,追了两部剧<了不起的麦瑟尔夫人& ...

  8. python中协程与函数的区别_深入浅析python 协程与go协程的区别

    进程.线程和协程 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定义: 操作系统能够进行运算调度的最小单位.它被包含在进 ...

  9. c++ 协程_理解Python协程(Coroutine)

    由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...

  10. python 协程和异步的关系_python 协程和异步I/O的实践

    协程的概念协程(coroutine)通常又称之为微线程或纤程,它是相互协作的一组子程序(函数).所谓相互协作指的是在执行函数A时,可以随时中断去执行函数B,然后又中断继续执行函数A.注意,这一过程并不 ...

最新文章

  1. 《Head First Python》笔记 第四章 持久存储
  2. JAVA单线程以及java多线程的实现方式
  3. python文字冒险游戏_模拟射击文字类游戏(Python)
  4. Hibernate保存数据自动生成主键出现奇怪异常Duplicate entry '0' for key 1
  5. C语言中返回错误信息的函数总结
  6. Bootstrap全局css样式_图片,响应式工具
  7. Winxp中加密自己的用户目录
  8. 工业以太网交换机的安装流程详解
  9. 【概率论】3-4:二维分布(Bivariate Distribution)
  10. 20. 顺时针打印矩阵
  11. 目录 1. 管理的门槛 1 1.1. 资历作为一个年龄效应 1 1.2. 高层次知识结构的构建与提升 系统层面及战略层面的问题时 1 2. ,一类是绝对年龄效应,另一类是相对年龄效应。 1 2.1.
  12. 【SDPVRP问题】基于遗传算法求解同时取送货的车辆路径问题含Matlab代码
  13. IOT | 物联网入门
  14. PPT实现单页点名的方式
  15. 浅谈数据监控数据分析
  16. 57期性能测试_0621
  17. 软件质量有什么特性?
  18. python round_Python3 round() 函数
  19. 技术分享 | 无人机集群——分布式控制算法
  20. 苹果三星好“基友”:专利诉讼不影响元件供应

热门文章

  1. C#中的String和string有什么区别?还有ref关键字怎么用?
  2. 用python画图代码简单-【Matplotlib】利用Python进行绘图
  3. python基础看什么书-2020年入门Python,这7本书一定要看!
  4. python难嘛-为什么说python入门很简单,但是在你这很难?
  5. python画树叶-使用Python turtle画分形树叶图
  6. python爬虫能干啥-Python爬虫还能干什么?
  7. python中怎么比较两个列表-python中如何比较两个列表
  8. python安装用32位还是64位-我应该使用Python 32位还是Python 64位
  9. python里面的之前打过的记忆信息-python 中 __init__方法
  10. 编程语言python特点-Python语言的特点有哪些?九大特点介绍!