首页 > 编程语言 >Python异步编程之yield from

Python异步编程之yield from

时间:2023-12-21 09:49:11浏览次数:41  
标签:异步 sub ... Python 生成器 yield gen asyncio

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

  1. 在调用包含yield from的函数时,程序会停在yield from 这里,并将for循环的执行传递到子生成器里面去。相当于直接调用子生成器。这个功能可以称之为传输通道
  2. 子生成器中的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秒

标签:异步,sub,...,Python,生成器,yield,gen,asyncio
From: https://www.cnblogs.com/goldsunshine/p/17918276.html

相关文章

  • python基础(一)常用数据类型
    Python3中有六个标准的数据类型:Number(数字)、String(字符串)、List(列表)、Tuple(元组)、Set(集合)、Dictionary(字典)1.数字类型:3种数值类型:int/float/bool/complex2.String(字符串):使用英文双引号或英文单引号括起来。如果有多行内容,除了使用换行符之外,还可以使用"""MMMM"""来定义3.List(......
  • BP神经网络思想及其Python实现
    神经网络是一种模拟人类大脑工作方式的计算模型。它由神经元组成,这些神经元相互连接形成网络。每个连接都有一个权重,而神经元则通过激活函数处理输入并生成输出。神经网络可以分为输入层、隐藏层和输出层。在训练过程中,神经网络通过调整权重来学习模式和特征,使其能够进行预测或分......
  • 如何使用Python进行地址信息(省/市/区/姓名/电话)提取
    地址信息提取大纲一级目录:地址信息提取的背景和目的二级目录:什么是地址信息提取地址信息提取是一种技术,它可以帮助我们从文本、图像或其他来源中提取出有关地址的信息。这种技术的背景和目的是为了解决人们在日常生活和工作中经常遇到的地址相关问题。无论是在线购物、导航应用还是......
  • Python之AI功能的实现
    使用Python我们可以调用AI库,实现一些AI功能。1.文本相似度:importdifflibs1="abc"s2="abce"ratio=difflib.SequenceMatcher(None,s1,s2).quick_ratio()print(ratio)2.情感分析:单句子情感分析fromsnownlpimportSnowNLPs="你人真好"nlp=SnowNLP(s)......
  • 【misc】[HNCTF 2022 WEEK2]calc_jail_beginner_level4.1(JAIL) --沙盒逃逸,python模板
    这道题没给附件,直接连上看看这里一开始用().__class__.__base__.__subclasses__()[-4].__init__.__globals__[bytes([115,121,115,116,101,109]).decode()](bytes([115,104]).decode())进行尝试,后面发现bytes函数被禁用了,可以用另外的函数代替().__class__.__base__.__subclasse......
  • python之列表的排序、循环、合并
    排序:sorted()显示临时排序cars=['byd','audi','gelly','qirui','chengcheng']print(sorted(cars))print(cars)结果:['audi','byd','chengcheng','gelly','qirui'][�......
  • Python图书目录提取标题序号、页码、标题内容
    切割获取标题需要,注意序号标题之间的空格与后面页码前的空格不一样;替换标题序号、页码去除前后空格获取标题内容;返回处理好的数据写入Excel,OK完成任务.book_contents.py#-*-coding=utf-8-*-importpandasaspd#切割字符获取标题序号、标题、页码defsplit_words(d......
  • Docker Alpine Linux 安装 Python3
      参考文档:DockerAlpineLinux安装Pytho1、DockerfileDockerfile文件一般包含基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令,’#’为Dockerfile中的注释。参考文件::Docker构建自定义镜像和Dockerfile文件2、直接安装 通过Dockerfile文件构建镜像时,直......
  • # yyds干货盘点 # 运行js文件,会弹出一个python解释器的界面,怎么解决呢?
    大家好,我是皮皮。一、前言前几天在Python白银交流群【菜......
  • 运行js文件,会弹出一个python解释器的界面,怎么解决呢?
    大家好,我是皮皮。一、前言前几天在Python白银交流群【菜......