由浅入深走进Python异步编程【协程与yield】(含代码实例讲解 || 迭代器、生成器、协程、yield from)

写在前面

从底层到第三方库,全面讲解python的异步编程。这节讲述的是python异步编程的底层原理第一节,详细了解需要配合下一节观看哦。纯干货,无概念,代码实例讲解。

本系列有6章左右,点击头像或者专栏查看更多内容,陆续更新,欢迎关注。

部分资料来源及参考链接:
https://www.bilibili.com/video/BV1Li4y1j7RY/
https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B

同步与异步

同步必须顺序执行,异步可以大家一起执行。但是同步由于是顺序执行,结果是有序的,异步的结构是无序的。

迭代与遍历

迭代常用于数组,列表,元组等有序结构;遍历则常用于二叉树,字典等无序结构。

为什么是这样呢?
这是因为迭代有一个next(见《数据结构》),这个next是有指向性的,但是遍历就是无序搜索

重点:

  1. 迭代比遍历要快
  2. 遍历可以强行用在数组,列表等有序结构,但迭代不能搜索无序结构
  3. (这就是为啥很多老师喜欢把迭代叫成遍历,这是一种误导初学者)

迭代器(iter)

你可以把任何有序的类型,转换为可迭代对象,就像这样

list_data = [0,1,2,3,4,5,6,7,8,9]

iterator_data = iter(list_data)#将可迭代对象 转成 迭代器
print(type(iterator_data))

你也可以使用next方法了,

print(type(iterator_data))

那么,如何创建迭代器呢?

迭代器具有特殊的__iter__,__next__方法

class Numbers:

    def __init__(self):
        self.a = 1

    def __iter__(self):#必须返回自己
 
        return self
 
    def __next__(self):
        data = self.a
        self.a+= 1
        return data

#'__iter__和__next__同时出现就是迭代器了,普通类并没有这两个方法'
NumBer = Numbers()

print(next(NumBer))
print(next(NumBer))
print(next(NumBer))

iter方法写法是固定的,next魔法方法会在使用next时自动调用。

生成器(generator)

相比迭代器,它多了一个yield,而这个关键字可以实现函数状态的挂起与恢复。用以下的代码来体会一下这个含义

def get_data():
    
    list_data = [0,1,2,3,4,5,6,7,8,9]
    for data in list_data:
        return data     #直接就终止了函数
        #yield data    #直接挂起了函数,等待下次恢复

generator_data = get_data()
print(generator_data)
print(type(get_data()))

在return的状态下,马上就会返回并退出函数。但是如何得到我们想要的结果呢,每一次都返回一个迭代出来的值,这就是yield,改成下面的代码:

def get_data():
    
    list_data = [0,1,2,3,4,5,6,7,8,9]
    for data in list_data:
        #return data     #直接就终止了函数
        yield data    #直接挂起了函数,等待下次恢复

generator_data = get_data()
print(generator_data)
print(type(get_data()))

输出结果为
在这里插入图片描述
返回了一个生成器,它是可以使用next方法来获取值的,执行print(next(generator_data))就输出了一个0,再次执行,就会一个个输出。

当然你也可以使用for来进行输出。

所以,其实这里就是yield与循环的巧妙应用。工作流程就像这样:

1.  方法中携带了yield关键字,首次调用时,会返回generator生成器。
2.  首次使用next,生成器激活,开始执行并在yield关键字位置返回,同时挂起当前函数状态。
3.  再次使用next,保存了之前的状态,从yield的下一行开始执行。

协程(coroutine)

协程是一个非常重要的概念。对于理解后续golang中的协程goroutine底层原理有很大帮助。这里先解释coroutine

对于协程,wiki百科有这样的信息

协程(英语:coroutine)是计算机程序的一类组件,推广了协作式多任务的子例程,允许执行被挂起与被恢复。

生成器,也叫作“半协程”,是协程的子集。尽管二者都可以yield多次,挂起(suspend)自身的执行,并允许在多个入口点重新进入,但它们特别差异在于,协程有能力控制在它让位之后哪个协程立即接续它来执行,而生成器不能,它只能把控制权转交给调用生成器的调用者。在生成器中的yield语句不指定要跳转到的协程,而是向父例程传递返回值。

(上述信息的参考链接如下)
https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B

通过上述信息,我们可以知道:协程是允许被挂起与被恢复的;协程是可以通过生成器实现的。再回到生成器的next方法,它做了什么呢?

next

官方文档是这样说的

generator.next()
开始一个生成器函数的执行或是从上次执行 yield 表达式的位置恢复执行。 当一个生成器函数通过 next() 方法恢复执行时,当前的 yield 表达式总是取值为 None。 随后会继续执行到下一个 yield 表达式,这时生成器将再次挂起,而 expression_list 的值会被返回给 next() 的调用方。 如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。

此方法通常是隐式地调用,例如通过 for 循环或是内置的 next() 函数。

(上述信息的参考链接如下)
https://docs.python.org/zh-cn/3/reference/expressions.html?highlight=send#generator.send

现在来调试一段代码,体会一下吧
在这里插入图片描述
此时没有输出。data是一个被挂起的生成器。这就说明,yield可以类似于return一样,终止函数运行。

接下来调用next方法
在这里插入图片描述
进入函数内部,同时指针指向‘挂起了’,yield即刻返回,没有给a赋值,也没有执行函数中的print语句
在这里插入图片描述
继续执行,再次跳入函数
在这里插入图片描述
此时再次恢复函数,执行print语句,但是a仍旧没有赋值
在这里插入图片描述
这里,没有产生下一个值,所以返回了stopiteration
在这里插入图片描述

现在,再倒回去看官方文档的解释就非常清楚了。

开始一个生成器函数的执行或是从上次执行 yield 表达式的位置恢复执行。 当一个生成器函数通过 next() 方法恢复执行时,当前的 yield 表达式总是取值为 None。 随后会继续执行到下一个 yield 表达式,这时生成器将再次挂起,而 expression_list 的值会被返回给 next() 的调用方。 如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。

send

还需要了解一个重要的send方法,其实就是前文中有一句 当一个生成器函数通过 __next__() 方法恢复执行时,当前的 yield 表达式总是取值为 None,而send方法可以更改这个取值的结果,就像这样
在这里插入图片描述
此时的程序结果就是
在这里插入图片描述
这里下面当然是报出了StopIteration错误,因为和next一样的,没有下一个值了。

官方文档解释:

恢复执行并向生成器函数“发送”一个值。 value 参数将成为当前 yield 表达式的结果。 send() 方法会返回生成器所产生的下一个值,或者如果生成器没有产生下一个值就退出则会引发 StopIteration。 当调用 send() 来启动生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。

捕获协程异常的值

对于生成器,它可以及时捕获异常值,用于检视多次恢复中可能产生的异常。

看看下面的代码

def get_data(data):
    
    a = yield data 
            
    if a is None:   #此处的None为结尾,直接抛出异常,结束生成器

        print('第一次send:恢复执行')
        return '结束了'

    print('send的值:{}'.format(a))

data = get_data('挂起了')

print('第一次next:{}'.format(next(data)))  #必须调用next() 才能用send()

try:
    data.send(None) #结束生成器
except StopIteration as e:  
    print('抛出异常:{}'.format(e.value))

执行结果:
在这里插入图片描述
简单解释一下这个代码,第一行结果就是首次激活,在yield挂起,第二行由于send None,进入if语句分支,print了恢复执行

然后有一个return语句,不知道return到了哪里。然后就会抛出StopIteration异常,因为send没有得到下一个值,此时弹出进入except分支,通过一个.value属性拿到了return的值

关键就在这里了,为什么这个异常,有一个value属性呢?

官方文档是这样说的:

exception StopIteration
由内置函数 next() 和 iterator 的 next() 方法所引发,用来表示该迭代器不能产生下一项。

value 该异常对象只有一个属性 value,它在构造该异常时作为参数给出,默认值为 None。

当一个 generator 或 coroutine 函数返回时,将引发一个新的 StopIteration实例,函数返回的值将被用作异常构造器的 value 形参。

上述资料链接:
https://docs.python.org/zh-cn/3/library/exceptions.html#StopIteration

简单来说就是,由于你有一个return,表明当前生成器或协程是必须结束的状态,不可能会得到下一个next值,此时会产生一个新的StopIteration实例,并把返回值填充至它的value属性中。

yield from

官网链接
https://docs.python.org/zh-cn/3/whatsnew/3.3.html#pep-380

官方文档是这样说的:

允许生成器将其部分操作委托给另一个生成器。这允许包含的一段代码被分解出来并放置在另一个生成器中。此外,允许子生成器返回一个值,并且该值可供委托生成器使用。

但是,与普通循环不同,它允许子生成器直接从调用范围接收发送和抛出的值,并将最终值返回到外部生成器

简单来说就是,yield from 和 for循环很类似,但yield from功能更强大,yield from 还能自动捕获StopIteration异常,并输出异常对象的value属性。

看下面这段代码

def accumulate():
    tally = 0
    while 1:
        next = yield
        if next is None:
            return tally
        tally += next

def gather_tallies(tallies):
    while 1:
        tally = yield from accumulate()
        tallies.append(tally)

tallies = []
acc = gather_tallies(tallies)
next(acc)  # Ensure the accumulator is ready to accept values
for i in range(4):
    acc.send(i)

acc.send(None)  # Finish the first tally
for i in range(5):
    acc.send(i)

acc.send(None)  # Finish the second tally


tallies
[6, 10]

这是官方的代码,这里就可以看出来。一旦输入的值发生了异常,我们就可以return退出生成器,同时利用yield from进行捕获,不需要再写try-except,和呼出value属性。

协程新写法

了解了半协程,来看看真协程是怎么写的。协程是一个函数,只是它满足以下特点:

1. 有 IO 依赖的操作
2. 可以在进行 IO 操作时暂停
3. 无法直接执行

第一点就是输入输出流,就是前面提到的send方法,yield from。第二点就是可以进行挂起。第三点表示要使用next来进行迭代,不能直接执行。

它的作用就是对有大量 IO 操作的程序进行加速
Python 协程属于 可等待 对象
因此可以在其他协程中被等待
注意:

  1. 协程是单线程的,只是控制自己,而多线程是切换线程,切换线程需要不少资源
  2. 协程可以和多线程一起用,达到最高效率

在3.5版本之前是这样的:

@asyncio.coroutine
def old_data():#旧写法
    print('正在执行')
    yield from asyncio.sleep(2)#沉睡2秒                              
    print('执行完毕')

此时将asyncio作为装饰器使用,使用了yield from来接受错误信息

asyncio

接上面,现在新版的写法是这样

import asyncio

async def new_data():
    print('正在执行')
    await asyncio.sleep(2)#沉睡2秒
    print('执行完毕')

asyncio.run(new_data())#运行新写法

@asyncio.coroutine 替换为 async
yield from 替换为 await

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/276117.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++学习实践(一)高频面试问题总结(附详细答案)

文章目录 一、基础常见面试题1、数组和链表区别2、深拷贝和浅拷贝相关问题的区别3、a和a区别4、c内存模型5、四种强制转换和应用场景 二、指针相关1、指针和引用的区别2、函数指针和指针函数3、传指针、引用和值4、常量指针和指针常量5、野指针6、智能指针的用法 三、关键字作用…

YOLOv8可视化:引入多种可视化CAM方法,为科研保驾护航

💡💡💡本文内容:调用pytorch下的CAM可视化库,支持十多种可视化方法,打开“黑盒”,让YOLOv8变得相对可解释性 收录 YOLOv8原创自研 https://blog.csdn.net/m0_63774211/category_12511737.html?spm=1001.2014.3001.5482 💡💡💡全网独家首发创新(原创),适…

桥接模式-举例

概叙:桥接模式用一种巧妙的方式处理多层继承存在的问题, 用抽象关联取代了传统的多层继承, 将类之间的静态继承关系转换为动态的对象组合关系, 使得系统更加灵活,并易于扩展, 同时有效控制了系统中类的个数…

企业如何购买腾讯云服务器?(详细指南)

腾讯云服务器购买流程直接在官方秒杀活动上购买比较划算,在云服务器CVM或轻量应用服务器页面自定义购买价格比较贵,但是自定义购买云服务器CPU内存带宽配置选择范围广,活动上购买只能选择固定的活动机,选择范围窄,但是…

专题四:前缀和

前缀和 一.一维前缀和(模板):1.思路一:暴力解法2.思路二:前缀和思路 二. 二维前缀和(模板):1.思路一:构造前缀和数组 三.寻找数组的中心下标:1.思路一:前缀和 四.除自身以外数组的乘积&#xff…

php最常出现的错误

目录 1. E_WARNING:为 foreach ()提供的参数无效 2. PDOException:拒绝SQLSTATEHY000连接 3.错误使用empty函数 1. E_WARNING:为 foreach ()提供的参数无效 PHP foreach构造在PHP 4中引入&am…

web3方向产品调研

每次互联网形态的改变,都会对世界产生很大的影响,上一次对社会产生重大影响的互联网形态(Web2.0)催生了一批改变人类生活和信息交互方式的企业。 目录 概述DAO是什么?为什么我们需要DAO? 金融服务金融桥接及周边服务D…

Unity中Shader 齐次坐标

文章目录 前言一、什么是齐次坐标二、齐次坐标增加分量 w 的意义1、当 w ≠ \neq  0时:2、当 w 0时:3、用方程组,直观的看一下w的意义 前言 在之前的文章中,我们进行了正交相机视图空间转化到裁剪空间的推导。 Unity中Shade…

3DMAX 中的 VR 渲染器如何设置局部区域渲染?

3DMAX 中的 VR 渲染器如何设置局部渲染? 首先我们要得打开渲染设置,在3damx里按F10,调出渲染设置。选定渲染器为Vary渲染器: 设置VR的局部渲染,需要打开帧缓冲,我们在V-ary项下,打开帧缓冲(点击…

腾讯云服务器怎么买划算?最新优惠价格表

2023腾讯云轻量应用服务器优惠价格表,12月最新报价,腾讯云轻量2核2G3M带宽62元一年、2核2G4M轻量服务器118元一年,540元三年、2核4G5M带宽218元一年,756元三年、4核8G12M轻量服务器646元15个月,CVM云服务器S5实例2核2G…

六、Redis 分布式系统

六、Redis 分布式系统 六、Redis 分布式系统6.1 数据分区算法6.1.1 顺序分区6.1.2 哈希分区 6.2 系统搭建与运行6.2.1 系统搭建6.2.2 系统启动与关闭 6.3 集群操作6.3.1 连接集群6.3.2 写入数据6.3.3 集群查询6.3.4 故障转移6.3.5 集群扩容6.3.6 集群收缩 6.4 分布式系统的限制…

vue整理面试题

1. v-if/v-show的区别? v-if"表达式" 当表达式值true,v-if所作用的元素显示 否则隐藏 v-show"表达式" 当表达式值true,v-if所作用的元素显示 否则隐藏 理解: v-if控制元素显示与隐藏,通过js创建dom元素或删除…

统信UOS linux下opencv应用编译时的头文件和库文件路径查找设置方法

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 一、引言 老猿原来进行的C和C开发主要是基于windows环境的,目前要在统信UOS操作系统环境下编译opencv应用程序,其环境设置与windows环境下变化很多,今天就来介绍一下在统…

AtCoder Beginner Contest 333

B - Pentagon 没什么好讲的&#xff0c;pass int a[N]; int len[6] { 0,1,2,2,1 }; void solve() {char s1, s2, t1, t2; cin >> s1 >> s2 >> t1 >> t2;if (s2 < s1) swap(s1, s2);if (t2 < t1) swap(t1, t2);int d1 s2 - s1, d2 t2 - t1;if…

设计模式——适配器模式(Adapter Pattern)

概述 适配器模式可以将一个类的接口和另一个类的接口匹配起来&#xff0c;而无须修改原来的适配者接口和抽象目标类接口。适配器模式(Adapter Pattern)&#xff1a;将一个接口转换成客户希望的另一个接口&#xff0c;使接口不兼容的那些类可以一起工作&#xff0c;其别名为包装…

第三方软件测试公司有哪些服务形式?如何收费?

由于软件企业的增多&#xff0c;企业更加注重软件开发&#xff0c;因此会将软件测试工作交由第三方软件测试公司进行。第三方软件测试公司也就是专门做软件测评的外包公司&#xff0c;主要是发现软件漏洞和缺陷以便公正、客观评估软件质量&#xff0c;再出具一份软件测试报告。…

verilog rs232串口模块

前面发了个发送模块&#xff0c;这次补齐&#xff0c;完整。 串口计数器&#xff0c;波特率适配 uart_clk.v module uart_clk(input wire clk,input wire rst_n,input wire tx_clk_en,input wire rx_clk_en,input wire[1:0] baud_sel,output wire tx_clk,output wire rx_clk )…

操作系统【设备管理】

设备管理 一、前言 学习了存储器管理后&#xff0c;继续学习设备管理&#xff0c;设备管理的主要功能有缓冲区管理、设备分配、设备处理、虚拟设备及实现设备独立性等&#xff0c;由于I/O设备不仅种类繁多&#xff0c;而且他们的特性和操作方式往往相差甚大&#xff0c;使得设…

【C++篇】讲解string容器及其操作

文章目录 &#x1f354;简述string容器⭐字符串拼接操作⭐查找和替换⭐字符串比较⭐插入和删除⭐获取字串 &#x1f354;简述string容器 在C STL中&#xff0c;string是一个字符串容器&#xff0c;它封装了字符串相关的操作&#xff0c;提供了很多方便的方法来处理字符串。 具…

Vue - Class和Style绑定详解

1. 模板部分 <template><div><!-- Class 绑定示例 --><div :class"{ active: isActive, text-danger: hasError }">Hello, Vue!</div><!-- Class 绑定数组示例 --><div :class"[activeClass, errorClass]">Cla…