Python异步编程之yield from

1|0yield 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 两个最重要的特点:

  1. 在调用包含yield from的函数时,程序会停在yield from 这里,并将for循环的执行传递到子生成器里面去。相当于直接调用子生成器。这个功能可以称之为传输通道
  2. 子生成器中的return,会被 res = yield from 捕获,并赋值给res。这个可以称之为异常处理

2|0传输通道


生成器存在这样一种调用场景,有生成器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 return100

执行流程:
当程序执行到 yield from 时,会暂停在这里,将for循环作用到内层迭代器,也就是g = sub_gen()中的g变量。直到sub_gen执行到return抛出异常被yield from捕获,这个调用算是结束。这就是yield from的传输通道

注意:除了可以通过yield from 传输通道的能力迭代取值,也可以通过send发送值到子生成器中

3|0异常处理


在上一篇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的异常捕获能力

4|0yield from 专用术语


yield from使用的专门术语:
委派生成器:包含 yield from  表达式的生成器函数;即上面的gen生成器函数
子生成器:yield from 从中取值的生成器;即上面的sub_gen生成器函数
调用方:调用委派生成器的客户端代码;即上面的main生成器函数

三者之间的关系如下:

委派生成器在 yield from 表达式处暂停时,调用方可以直接迭代子生成器,子生成器把产出的值发给调用方。子生成器返回之后,解释器会抛出StopIteration 异常,yield from会捕获异常并取值,然后委派生成器会恢复。

5|0yield 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

__EOF__

本文作者goldsunshine
本文链接https://www.cnblogs.com/goldsunshine/p/17918276.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   金色旭光  阅读(370)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
历史上的今天:
2018-12-21 SQLALlchemy数据查询小集合
点击右上角即可分享
微信分享提示