Python异步编程之yield from
yield from 简介
yield from 是Python3.3 后新加的语言结构,可用于简化yield表达式的使用。
yield from 简单示例:
>>> def gen():
... yield from range(10)
...
>>> g = gen()
>>> next(g)
0
>>> next(g)
1
>>>
yield from 用于获取生成器中的值,是对yield使用的一种优化。
yield from 两个最重要的特点:
- 在调用包含
yield from
的函数时,程序会停在yield from
这里,并将for循环的执行传递到子生成器里面去。相当于直接调用子生成器。这个功能可以称之为传输通道
- 子生成器中的return,会被
res = yield from
捕获,并赋值给res。这个可以称之为异常处理
传输通道
生成器存在这样一种调用场景,有生成器A,生成器B调用A迭代取值。main函数从迭代生成器B获取元素。这就是所谓的嵌套生成器。如果要迭代出最内层生成器的值,可以用如下方法:
>>> def sub_gen():
... for i in range(5):
... yield i
... return 100
...
>>> def gen():
... g = sub_gen()
... while True:
... try:
... temp = next(g)
... yield temp
... except StopIteration as e:
... print(f"sub_gen return {e.value}")
... return
...
>>> g = gen()
>>> for i in g:
... print(i)
...
0
1
2
3
4
sub_gen return 100
首先在外层生成器中使用while True 循环,通过next迭代内层生成器的值,然后捕获异常获取内层生成器的返回。
使用 yield from 不需要这两个动作就能完成同样的功能,有效减少代码复杂度。
>>> def sub_gen():
... for i in range(5):
... yield i
... return 100
...
>>> def gen():
... g = sub_gen()
... res = yield from g
... print(f"sub_gen return:{res}")
...
>>> g = gen()
>>> for i in g:
... print(i)
...
0
1
2
3
4
sub_gen return:100
执行流程:
当程序执行到 yield from 时,会暂停在这里,将for循环作用到内层迭代器,也就是g = sub_gen()
中的g变量。直到sub_gen执行到return抛出异常被yield from捕获,这个调用算是结束。这就是yield from的传输通道
注意:除了可以通过yield from 传输通道的能力迭代取值,也可以通过send发送值到子生成器中
异常处理
在上一篇yield使用中说明过迭代生成器时遇到return会抛出异常,获取返回值需要捕获异常再取值,而yield from 的功能之二就是捕获了异常,获取到return的值,赋值给变量。
def sub_gen():
for i in range(5):
yield i
return 100
def gen():
g = sub_gen()
res = yield from g
print(f"捕获返回值:{res}")
def main():
g = gen()
for i in g:
print(i)
main()
执行过程:
使用for循环迭代g,相当于for循环迭代sub_gen()。
sub_gen 生成器最后的返回值作为异常抛出,调用方需要捕获异常才能获取返回值。但是有了yield from
之后,sub_gen生成器的返回值异常就会被yield from捕获,赋值给res变量。这就是yield from能够处理内层生成器的返回值。这就是yield from的异常捕获能力
yield from 专用术语
yield from使用的专门术语:
委派生成器
:包含 yield from 表达式的生成器函数;即上面的gen生成器函数
子生成器
:yield from 从中取值的生成器;即上面的sub_gen生成器函数
调用方
:调用委派生成器的客户端代码;即上面的main生成器函数
三者之间的关系如下:
委派生成器在 yield from 表达式处暂停时,调用方可以直接迭代子生成器,子生成器把产出的值发给调用方。子生成器返回之后,解释器会抛出StopIteration 异常,yield from会捕获异常并取值,然后委派生成器会恢复。
yield from 实现的协程
在Python中有多种方式可以实现协程,例如:
- greenlet 是一个第三方模块,用于实现协程代码(Gevent协程就是基于greenlet实现)
- yield 生成器,借助生成器的特点也可以实现协程代码。
- asyncio 在Python3.4中引入的模块用于编写协程代码。
- async & awiat 在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码。
在Python3.4之前官方未提供协程的类库,一般大家都是使用greenlet等其他来实现。在Python3.4发布后官方正式支持协程,即:asyncio模块。
在Python3.4-Python3.11的代码中可以通过asyncio + yield from的方法来实现原生协程。
import time
import asyncio
@asyncio.coroutine
def task1():
print('开始运行IO任务1...')
yield from asyncio.sleep(2) # 假设该任务耗时2s
print('IO任务1已完成,耗时2s')
return task1.__name__
@asyncio.coroutine
def task2():
print('开始运行IO任务2...')
yield from asyncio.sleep(3) # 假设该任务耗时3s
print('IO任务2已完成,耗时3s')
return task2.__name__
@asyncio.coroutine
def main():
# 把所有任务添加到task中
tasks = [task1(), task2()]
# 子生成器
done, pending = yield from asyncio.wait(tasks)
# done和pending都是一个任务,所以返回结果需要逐个调用result()
for r in done:
print(f'协程返回值:r.result()')
if __name__ == '__main__':
start = time.time()
# 创建一个事件循环对象loop
loop = asyncio.get_event_loop()
try:
# 完成事件循环,直到最后一个任务结束
loop.run_until_complete(main())
finally:
loop.close()
print('所有IO任务总耗时%.5f秒' % float(time.time()-start))
代码解释:
- @asyncio.coroutine 装饰的生成器函数代表着一个任务
- yield from asyncio.sleep(3) 模拟一个IO操作,协程遇到IO会自动切换
- loop.run_until_complete(main()) 启动一个事件循环,在循环中执行所有任务。任务遇到IO自动切换
输出:
/Users/yield_from_demo.py:14: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
def task2():
/Users/yield_from_demo.py:22: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
def main():
/Users/yield_from_demo.py:28: DeprecationWarning: The explicit passing of coroutine objects to asyncio.wait() is deprecated since Python 3.8, and scheduled for removal in Python 3.11.
done, pending = yield from asyncio.wait(tasks)
开始运行IO任务1...
开始运行IO任务2...
IO任务1已完成,耗时2s
IO任务2已完成,耗时3s
协程返回值:r.result()
协程返回值:r.result()
所有IO任务总耗时3.00188秒
Python异步编程之yield from的更多相关文章
- python异步编程之asyncio
python异步编程之asyncio 前言:python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病.然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率, ...
- python异步编程之asyncio(百万并发)
前言:python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病.然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率,弥补了python性能方面的短板,如最 ...
- python函数式编程之yield表达式形式
先来看一个例子 def foo(): print("starting...") while True: res = yield print("res:",res ...
- 异步编程之Generator(1)——领略魅力
异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...
- Python 多进程编程之multiprocessing--Pool
Python 多进程编程之multiprocessing--Pool ----当需要创建的子进程数量不多的时候,可以直接利用multiprocessing 中的Process 动态生成多个进程, -- ...
- python并发编程之gevent协程(四)
协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...
- python并发编程之asyncio协程(三)
协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...
- python并发编程之multiprocessing进程(二)
python的multiprocessing模块是用来创建多进程的,下面对multiprocessing总结一下使用记录. 系列文章 python并发编程之threading线程(一) python并 ...
- 异步编程之co——源码分析
异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...
- 异步编程之Generator(2)——剖析特性
异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...
随机推荐
- 使用Jenkins构建镜像:将应用打包成镜像
学习某册子的CICD,记录使用Jenkins构建镜像的过程. 使用Jenkins集成Git来构建Docker镜像,为后面的部署准备镜像资源. 1. 安装Nodejs环境 如果想要安装Node环境,有以 ...
- 哪一个更好?Spring boot还是Node.js
前言 本篇文章有些与众不同,由于我自己手头有些关于这个主题的个人经验,受其启发写出此文.虽然SpringBoot和Node.js服务于很不一样的场景,但是这两个框架共性惊人.其实每种语言都有不计其数的 ...
- 视觉BEV基本原理和方案解析
BEV(Bird's-Eye-View)是一种鸟瞰视图的传感器数据表示方法,它的相关技术在自动驾驶领域已经成了"标配",纷纷在新能源汽车.芯片设计等行业相继量产落地.BEV同样在高 ...
- NPOI获取Excel文件里的形状/图片的坐标/锚点
有个妹纸找我请教如何获取图片坐标,因此我到家后花了点时间,写了这份代码. 实测下来,可以正确获取 Excel 2003 版本的形状和图片这两种的坐标/锚点,以及 Excel 2007 版本的图片的坐标 ...
- LabVIEW基于机器视觉的实验室设备管理系统(5)
目录 行动计划 设备借用 判断设备ID是否正确.设备是否在库 判断是否为已注册用户.电话是否正确 借出设备 设备归还 信息查询 判断ID是否正确.选择设备状态 效果演示 今天这一期,我们就来完成实验 ...
- 手撕Vue-Router-添加全局$router属性
前言 经过上一篇文章的介绍,完成了初始化路由相关信息的内容,接下来我们需要将路由信息挂载到Vue实例上,这样我们就可以在Vue实例中使用路由信息了. 简而言之就是给每一个Vue实例添加一个$route ...
- 基于.NET Core + Quartz.NET+ Vue + IView开箱即用的定时任务UI
前言 定时任务调度应该是平时业务开发中比较常见的需求,比如说微信文章定时发布.定时更新某一个业务状态.定时删除一些冗余数据等等.今天给大家推荐一个基于.NET Core + Quartz.NET + ...
- 组合式api-通过reactive和ref提供响应式数据
在setup中如果是直接定义遍历数据并不是响应式数据,和vue2中的data选项提供的数据不一样,vue2的data中返回的数据全部都是响应式数据. <script setup> // 这 ...
- 神经网络优化篇:如何理解 dropout(Understanding Dropout)
理解 dropout Dropout可以随机删除网络中的神经单元,为什么可以通过正则化发挥如此大的作用呢? 直观上理解:不要依赖于任何一个特征,因为该单元的输入可能随时被清除,因此该单元通过这种方式传 ...
- 可视化大屏与GIS之间如何实现互补?
在当今数字化时代,可视化大屏和地理信息系统(GIS)是两个在不同领域发挥重要作用的技术.可视化大屏以其生动.直观的图表.图像和动画展示方式,为数据可视化和信息展示提供了强大的工具.而GIS则通过地理空 ...