python 并发专题(四):yield以及 yield from
一、yield
python中yield的用法很像return,都是提供一个返回值,但是yield和return的最大区别在于,return一旦返回,则代码段执行结束,但是yield在返回值以后,会交出CUP的使用权,代码段并没有直接结束,而是在此处中断。
当调用send()或者next()方法之后,yield可以从之前中断的地方继续执行。
在一个函数中,使用yield关键字,则当前的函数会变成生成器。
实例解析:
生成一个斐波那契数列。
def fib(n):
index = 0
a = 0
b = 1 while index < n:
yield b
a,b = b, a+b
index += 1
1.生成器对象
fib = fib(100)
print(fib)
打印出来的结果是一个生成器对象,并没有直接把我们想要的值打印出来。
2.next()方法
fib = fib(100)
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
它的执行顺序是这样的,每次yield返回之后,程序将会中断,当出现next(fib)之后,程序将会从之前中断的地方继续执行。 python新版本中,不再提供fib.next()
方法。
3 send()方法
使用send()方法允许我们向生成器中传值。
import time def fib(n):
index = 0
a = 0
b = 1 while index < n:
sleep = yield b
print('等待%s秒' %sleep)
time.sleep(sleep)
a,b = b, a+b
index += 1 fib = fib(20)
print(fib.send(None)) # 效果等同于print(next(fib))
print(fib.send(2))
print(fib.send(2))
print(fib.send(2))
print(fib.send(2))
执行顺序如下:
首先,创建生成器对象
调用fib.send(None)方法,此处作用与next(fib)相同,程序返回当前b的值1, 程序中断。
调用fib.send(2)方法,程序被唤醒,将2传递给yield之前的变量sleep,程序继续运行,直到遇到yield将新的b返回,程序再次中断。
如此继续下去,直到程序结束。
二、yield from
例子一:生成器嵌套,简化代码
前面的都是单一层次的生成器,并没有嵌套,如果是多个生成器嵌套会怎么样呢,下面是一个例子。
def fun_inner():
i = 0
while True:
i = yield i def fun_outer():
a = 0
b = 1
inner = fun_inner()
inner.send(None)
while True:
a = inner.send(b)
b = yield a if __name__ == '__main__':
outer = fun_outer()
outer.send(None)
for i in range(5):
print(outer.send(i))
在两层嵌套的情况下,值的传递方式是,先把值传递给外层生成器,外层生成器再将值传递给外层生成器,内层生成器在将值反向传递给外层生成器,最终yield出结果。如果嵌套的层次更多,传递将会越麻烦。
下面是yield from的实现方式:
def fun_inner():
i = 0
while True:
i = yield i def fun_outer():
yield from fun_inner() if __name__ == '__main__':
outer = fun_outer()
outer.send(None)
for i in range(5):
print(outer.send(i))
效果是一样的,但是明显的代码量减少了,嵌套传值的时候,并不需要我们手动实现。
例子二: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]
例子三:使用 yield from 链接可迭代的对象
>>> def chain(*iterables):
... for it in iterables:
... yield from it
...
>>> s = 'ABC'
>>> t = tuple(range(3))
>>> list(chain(s, t))
['A', 'B', 'C', 0, 1, 2]
yield from x 表达式对 x 对象所做的第一件事是,调用 iter(x),从中获取迭代器。因此,x 可以是任何可迭代的对象
yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职责
例子四:
委派生成器
包含 yield from <iterable> 表达式的生成器函数。
子生成器
从 yield from 表达式中 <iterable> 部分获取的生成器。这就是PEP 380 的标题(“Syntax for Delegating to a Subgenerator”)中所说的“子生成器”(subgenerator)。
调用方
PEP 380 使用“调用方”这个术语指代调用委派生成器的客户端代码。在不同的语境中,我会使用“客户端”代替“调用方”,以此与委派生成器(也是调用方,因为它调用了子生成器)区分开。
委派生成器在 yield from 表达式处暂停时,调用方可以直接把数据发给子生成器,子生成器再把产出的值发给调用方。子生成器返回之后,解释器会抛出 StopIteration 异常,并把返回值附加到异常对象上,此时委派生成器会恢复。
from collections import namedtuple Result = namedtuple('Result', 'count average') # 子生成器
def averager(): #这里作为子生成器使用
total = 0.0
count = 0
average = None
while True:
term = yield #main 函数中的客户代码发送的各个值绑定到这里的 term 变量上
if term is None: #至关重要的终止条件。如果不这么做,使用 yield from 调用这个协程的生成器会永远阻塞。
break
total += term
count += 1
average = total/count
return Result(count, average) #返回的 Result 会成为 grouper 函数中 yield from 表达式的值。 # 委派生成器
def grouper(results, key): #grouper 是委派生成器
while True: #这个循环每次迭代时会新建一个 averager 实例;每个实例都是作为协程使用的生成器对象。
results[key] = yield from averager() #grouper 发送的每个值都会经由 yield from 处理,通过管道传给averager 实例。
#grouper 会在 yield from 表达式处暂停,等待averager 实例处理客户端发来的值。
#averager 实例运行完毕后,返回的值绑定到 results[key] 上。
#while 循环会不断创建 averager 实例,处理更多的值。 # 客户端代码,即调用方
def main(data): #main 函数是客户端代码,用 PEP 380 定义的术语来说,是“调用方”。这是驱动一切的函数。
results = {}
for key, values in data.items():
group = grouper(results, key) #group 是调用 grouper 函数得到的生成器对象,传给 grouper 函数的第一个参数是 results,用于收集结果;
#第二个参数是某个键。group 作为协程使用
next(group) #预激 group 协程。
for value in values:
group.send(value) #把各个 value 传给 grouper。传入的值最终到达 averager 函数中term = yield 那一行;
#grouper 永远不知道传入的值是什么。
group.send(None) # 重要! ⓬把 None 传入 grouper,导致当前的 averager 实例终止,也让grouper 继续运行,
#再创建一个 averager 实例,处理下一组值。
# print(results) # 如果要调试,去掉注释
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.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
'girls;m':
[1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
'boys;kg':
[39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
'boys;m':
[1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
} if __name__ == '__main__':
main(data)
运作方式说明
1.外层 for 循环每次迭代会新建一个 grouper 实例,赋值给 group变量;group 是委派生成器。
2.调用 next(group),预激委派生成器 grouper,此时进入 whileTrue 循环,调用子生成器 averager 后,在 yield from 表达式处暂停。
3.内层 for 循环调用 group.send(value),直接把值传给子生成器averager。同时,当前的 grouper 实例(group)在 yield from 表达式处暂停。
4.内层循环结束后,group 实例依旧在 yield from 表达式处暂停,因此,grouper 函数定义体中为 results[key] 赋值的语句还没有执行。
5.如果外层 for 循环的末尾没有 group.send(None),那么averager 子生成器永远不会终止,委派生成器 group 永远不会再次激活,因此永远不会为 results[key] 赋值。
6.外层 for 循环重新迭代时会新建一个 grouper 实例,然后绑定到group 变量上。前一个 grouper 实例(以及它创建的尚未终止的averager 子生成器实例)被垃圾回收程序回收。
综述
(把迭代器当作生成器使用,相当于把子生成器的定义体内联在yield from 表达式中。此外,子生成器可以执行 return 语句,返回一个值,而返回的值会成为 yield from 表达式的值)
例子四 阐明了下述四点:
1.子生成器产出的值都直接传给委派生成器的调用方(即客户端代码)。
2.使用 send() 方法发给委派生成器的值都直接传给子生成器。如果发送的值是 None,那么会调用子生成器的 __next__() 方法。如果发送的值不是 None,那么会调用子生成器的 send() 方法。如果调用的方法抛出 StopIteration 异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。
3.生成器退出时,生成器(或子生成器)中的 return expr 表达式会触发 StopIteration(expr) 异常抛出。
4.yield from 表达式的值是子生成器终止时传给 StopIteration异常的第一个参数。
yield from 结构的另外两个特性与异常和终止有关。
1.传入委派生成器的异常,除了 GeneratorExit 之外都传给子生成器的 throw() 方法。如果调用 throw() 方法时抛出StopIteration 异常,委派生成器恢复运行。StopIteration 之外的异常会向上冒泡,传给委派生成器。
2.如果把 GeneratorExit 异常传入委派生成器,或者在委派生成器上调用 close() 方法,那么在子生成器上调用 close() 方法,如果它有的话。如果调用 close() 方法导致异常抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出GeneratorExit 异常。
协程:通过客户调用 .send(...) 方法发送数据或使用 yield from 结构驱动的生成器函数。
python 并发专题(四):yield以及 yield from的更多相关文章
- Python并发编程之深入理解yield from语法(八)
大家好,并发编程 进入第八篇. 直到上一篇,我们终于迎来了Python并发编程中,最高级.最重要.当然也是最难的知识点--协程. 当你看到这一篇的时候,请确保你对生成器的知识,有一定的了解.当然不了解 ...
- python 并发专题(十二):基础部分补充(四)协程
相关概念: 协程:一个线程并发的处理任务 串行:一个线程执行一个任务,执行完毕之后,执行下一个任务 并行:多个CPU执行多个任务,4个CPU执行4个任务 并发:一个CPU执行多个任务,看起来像是同时执 ...
- python 并发专题(十四):asyncio (三)实战
https://www.cnblogs.com/wongbingming/p/9124142.html 在实战中,将会用到以下知识点: 多线程的基本使用 Queue消息队列的使用 Redis的基本使用 ...
- python 并发专题(一):并发基础相关概念,术语等
一.线程 1.概念 线程是程序执行流的最小执行单位,是行程中的实际运作单位. 进程是一个动态的过程,是一个活动的实体.简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执 ...
- python 并发专题(五):离散事件仿真(事件循环生成器)
出租车队运营仿真 创建几辆出租车,每辆车会拉几个乘客,然后回家.出租车首先驶离车库,四处徘徊,寻找乘客:拉到乘客后,行程开始:乘客下车后,继续四处徘徊. 程序解释 程序的输出示例: 创建 3 辆出租车 ...
- python 并发专题(二):python线程以及线程池相关以及实现
一 多线程实现 线程模块 - 多线程主要的内容:直接进行多线程操作,线程同步,带队列的多线程: Python3 通过两个标准库 _thread 和 threading 提供对线程的支持. _threa ...
- python 并发专题(十三):asyncio (一) 初识
https://www.cnblogs.com/wongbingming/p/9095243.html . 本文目录# 如何定义/创建协程 asyncio的几个概念 学习协程是如何工作的 await与 ...
- python 并发专题(七):Twisted相关函数以及实现
一.基础原理 二.基本函数 三.爬虫实现 四.web服务器与客户端实现
- python 并发专题(六):协程相关函数以及实现(gevent)
文档资源 http://sdiehl.github.io/gevent-tutorial/ 一.协程实现 线程和协程 既然我们上面也说了,协程也被称为微线程,下面对比一下协程和线程: 线程之间需要上下 ...
随机推荐
- Cobbler自动化部署系统
1.cobbler简介 cobbler是一个LInux服务器安装的服务,可以通过网络启动(PXE)的方式来快速安装.重装物理服务器和虚拟机,同时还可以管理DHCP,DNS等 cobbler可以 ...
- (五)使用logback进行日志记录
原文:https://www.cnblogs.com/taiyonghai/p/9290641.html 引入jar包 此处如果是引用了spring boot则不需要再引一下的jar包了,spring ...
- Jlink设置正确,但下载程序失败
[图中reset and run]勾选后即每次·下载程序后会自动复位,不需要再在硬件上进行复位 各参数设置正确 但依然下载失败. 原因是需要重新再编译一次,因为上次设置错误,编译后目标未创建! 重新编 ...
- Linux中bash的一些命令
Linux——bash的简单使用 bash及其特性: 1.bash实质上是一个可执行的程序,一个用户的工作环境. 2.每一个shell下可以再打开一个shell,新打开的shell称为子shell,每 ...
- 慕课网--java权限管理系统
http://coding.imooc.com/class/evaluation/149.html
- vue element安装
element-ui插件 安装 >: cnpm i element-ui -S main.js配置 import ElementUI from 'element-ui'; import 'ele ...
- C# CLosedXML四句代码搞定DataTable数据导出到Excel
最近用到DataTable导出到Excel,网上看了一下,都不怎么好使,逛了下GitHub一下完美解决了 用到的.net库CLosedXML,这个库用于读取,处理和写入Excel 2007+(.xls ...
- IDEA2019版中文汉化包
废话不多说,上才艺 E G M~~~~~ 2020版的IDEA大佬可以无视........ 1.打开IDEA文件目录 2.打开lib目录--将汉化版复制到该目录下 3.打开IDEA查看效果 高铁链 ...
- python实现将大文件夹分割成多个子文件夹
楼主用的linux,一旦数据达到几万,文件夹打开就会变卡,同时也方便同时分工协作,便于git管理,写了个将大文件夹分割成多个小文件夹的脚本 如操作文件夹:img,脚本不破坏img的数据,创建img_1 ...
- IIS发布之后可能出现的问题集及解决方案
1,首先注意发布的net目标框架,和发布的服务器所使用的框架是否一致