python中和生成器协程相关的yield之最详最强解释,一看就懂(一)

摘要:
yield是python中一个非常重要的关键词,所有迭代器都是yield实现的,学习python,如果不把这个yield的意思和用法彻底搞清楚,学习python的生成器,协程和异步io的时候,就会彻底懵逼。所以写一篇总结讲讲yield的东西。导致send抛出StopIteration异常三。

yield是python中一个非常重要的关键词,所有迭代器都是yield实现的,学习python,如果不把这个yield的意思和用法彻底搞清楚,学习python的生成器,协程和异步io的时候,就会彻底懵逼。所以写一篇总结讲讲yield的东西。

分成四块来讲, 这篇先说yield基本用法,后面会重点将yield from的牛逼之处

一, 生成器中使用yield

语法形式:yield <表达式>

这种情况,可以简单的把它理解为 return <表达式>, 每次next调用,会触发生成器从上一次停止的地方开始执行, 直到下一次遇到yield。 如果是第一次next,就是那个从函数开始知道第一个yield;以后每次next,则都是从上次yiled停止的地方开始, 继续执行,直到遇到下一次yiled, 如果一直执行到函数结束,都没有下个yield遇到, 则生成器运行终止,此时next会在生成器运行终止时,抛出StopIteration异常。 比如下面的代码

1 defgenerator():
2     print("gen-0:    start")
3     for i in range(2):
4         print("gen-%s-a:    i=%s" % (i,i))
5         yield i + 1                          #可简单理解为 return i + 1, 并等待
6         print("gen-%s-b:    i=%s" % (i,i))
7     print("gen-c")
8     yield 3                                  #可简单理解为 return 3, 并等待
9     print("gen-d")                           #此处继续执行完成后,由于后续在无其它yield语句,调用next的代码会抛出StopIteration异常
10 
11 try:
12     g = generator()                          #由于generator是一个包含yield关键字的生成器,此处直接调用gengerator()只是返回该生成器实例,实际无任何输出
13   print('main-0:    start')                        
14     n = next(g)                              #next触发生成器执行,由于是第一次触发,所以从generator循环外第2行和第4行的打印会输出,到第5行遇到yield后返回i+1到主程序,第6行不会执行
15     print('main-1:    n=%s' % n)
16     n = next(g)                              #next第二次触发生成器执行,由于是第二次触发,上次执行到第5行, 此时从第6行开始执行, 将输出第6行和第4行的打印, 并再次在第5行返回
17    print('main-2:    n=%s' % n)
18     n = next(g)                              #next第三次触发生成器执行,这次generator内部for循环已经完成,故执行完跳出循环执行到第8行后返回
19    print('main-3:    n=%s' % n)
20     n = next(g)                              #next视图第四次触发生成器执行,从第9行开始执行,但此后generator已经全部执行完成,所以此时next会抛出StopIteration异常,赋值无法完成
21     print('main-5:    n=%s' % n)             #异常产生, 此行将永远不会被执行
22 exceptStopIteration:
23     print('StopIteration n=%s' % n)          #第17行的next将触发异常

上面这段代码执行输出如下, 各位看官自行对照代码和详细注释很容易看懂

main-0: start
gen-0: start
gen-0-a: i=0
main-1: n=1
gen-0-b: i=0
gen-1-a: i=1
main-2: n=2
gen-1-b: i=1
gen-c
main-3: n=3
gen-d
StopIteration n=3

二, 可以接收传入参数值的yield

语法形式:x = yield <表达式>

next方式触发生成器无法传入参数, 如果想触发generator的同时, 传入参数给生成器,是否可以喃?答案是可以的, 这时候需要做两个改变,一是在生成器内增加变量接收该参数, 即本届标题形式;二是外部调用时, 不使用next, 而采用用调用生成器的send方法即可

1 def coroutine():                                    #这个生成器,我们其名coroutine, 意思是协程,协程和生成器都是使用yield来实现, 但本质上不一个概念
2     print("coroutine:    start")
3     for i in range(2):
4         print("coroutine-a:    i=%s" % i)
5         x = yield i + 1                                #由send传入的参数值将被赋值给x, 注意i+1的值是被yield用来返回的,不会赋值给x !
6         print("coroutine-b:    i=%s, x=%s" % (i,x))
7  
8 cr = coroutine()                                    #创建一个生成器实例
9 next(cr)                                            #生成器的第一次触发必须使用next,此时如果试图用send发送参数,将导致异常败,原因是此时生成器还未启动
10 try:
11     print("main-a:")
12     y = cr.send(0)                                #调用生成器的send方法, 将10传给生成器, 并触发一次生成器的执行
13     print("main-b:        y=%s" % y)
14     y = cr.send(1)                                #调用生成器的send方法, 将1传给生成器, 并触发一次生成器的执行
15     print("main-c:        y=%s" % y)                #14行的消息发送将导致StopIteration异常产生, 此行将永远不会被执行
16 exceptStopIteration:
17     print("StopIteration")

以下是执行后的输出

coroutine:      start
coroutine-a:    i=0
main-a:
coroutine-b:    i=0, x=0
coroutine-a:    i=1
main-b:         y=2
coroutine-b:    i=1, x=1
StopIteration

简单再解释一下,

1. 第一次执行next时,生成器通过yield i + 1 返回1, 并停在第5行等待被唤醒, 此时赋值动作尚未发生, x的值只是个None, 同时也为执行后面的print。

2. 生成器通过next进行过首次触发后,可以用send发送参数,比如第11行和12行,生成器内部变量先分别被赋值为 0和1, 大家可以对照输出看

3. 由于生成器总共只有两次yield的机会, next消耗一次,第一次send消耗一次,所以第二次send后,虽然不影响执行完参数传递给生成器的动作, 但由于生成器自身找不下一次yield的机会,生成器执行终止。 导致send抛出StopIteration异常

三。生成器本身的返回值

不知道大家注意到没有, 直接类似的cr = coroutine() 的调用,只是产生一个生成器实例,并没执行。而通过yield的返回时, 生成器本身的逻辑实际上都是为走完的, yield英文是退让的意思,除了无限序列的生成器,生成器最终都会执行完成,那么此时如果生成器通过return正常返回一个值,生成器的使用者能获得吗 ?比如下面的代码, 生成器正常结束后return的代码, 使用方如何获得喃 ?比如下面代码如何获取第10行正常终止后的返回值10

1 defgenerator():
2     yield 5                        
3     return 10                    #这个返回值如何获得 ?
4 
5 cr =generator()
6 n = next(cr)                     # yield 5返回的值可以被获得并赋值给n

答案是在StopIteration异常的处理逻辑中可以获得,方法如下, 只需要在上面代码后面继续加上以下处理

try:
    next(cr)
except StopIteration as e:        #通过except ... as 将异常保存在变量e中
    rt = e.value                #异常变量e的value值就是生成器通过return返回值

严格意义上讲, python将StopIteration定义为一个异常可能是不得已的事情, 因为这个异常的意思, 实际就是告诉使用者, “我没发生成下一个了, yield次数已经用完了, 我的使命结束了”, 从这个意义上讲, StopIteration也可以归为是正常逻辑, 所以强烈建议所有使用用生成器的地方,都应该要加上StopIteration异常处理。

四,不用next触发生成器执行

生成器的首次触发一定要使用next或者send(None)触发,这个有时候比较麻烦, 有没有不需要写next的情况喃。 答案是肯定的。我们可以把generator放在循环里面,比如把第一个例子的几次next改成一个for循环

defgenerator():
    print("gen-0:    start")
    for i in range(2):
        yield i + 1                          
    yield 3                                 
try:
    g =generator()                            
    for n in g:                            #for循环是通过next触发生成器实现的, 内部会调用next(g)
        print(n)
exceptStopIteration:
    print('StopIteration n=%s' % n)                

将得到下面的输出, 因为在python里面所有的迭代,包括循环语句,实际底层的实现都是通过生成器搞定的, 所以在for循环里面, 实际上是python内部实现在帮你调用next来触发生成器.和我们在第一例中的显示依次调用next(g)是一样的 可以看到第一次打印了“start”输出, 后面依次输出1,2,3. 但没有StopExeption抛出, 那是因为for的语意是循环次数和实际相同, 所以最后一次next被for内部消化了, 没有暴露出来而已

gen-0:  start
1
2
3

四。 再说 x = yield i, 生成器和协程

我们在来看一下上面第二部分的例子, 这里的x = yield i + 1语句, 实际上使函数本身同时具备了生产者和消费的功能, yield i + 1会让每次执行产生一个值, 这是生成者,而 x = yiled又让它可以接收一个send所发送的值。这样看起来同时具备了双重功能, 但这却是一种不好的用法, 应该尽量避免。而是让一个函数功能单一, 要吗作为生产器,要吗作为仅具备消费功能的协程。虽然协程和生成器都是用yield来实现的, 但不应该将二者功能混淆, 这可能会导致一些难以理解的代码,是不推荐这么用的。 推荐的用发是分开,要吗作为生成器,要吗作为协程,不要让一个函数同时兼备二者的功能

第一节是作为生成器的例子

作为协程, 建议的方式是使用 x = (yield), 不要让yiled生成任何值, 仅仅用于接收

 def coroutine():                                    #这个是协程,消费每次收到的值
     print("coroutine:    start")
     While True:
          print("coroutine-a:    i=%s" %i)
          x = (yield) 
          print("coroutine-b:    x=%s" % x)

下一篇 : python中和生成器协程相关的yield from之最详最强解释,一看就懂(二)

免责声明:文章转载自《python中和生成器协程相关的yield之最详最强解释,一看就懂(一)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇01-Hive综述(hive的安装、修改配置、基本使用)将tomcat添加到开机自动启动服务中下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Python学习—数据库篇之索引

一、索引简介 索引,是数据库中专门用于帮助用户快速查询数据的一种数据结构。类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置,然后直接获取即可,对于索引,会保存在额外的文件中。在mysql数据库中,索引是按照B树的结构来进行存储的。                              30                      ...

flask请求钩子、HTTP响应、响应报文、重定向、手动返回错误码、修改MIME类型、jsonify()方法 --

请求钩子: 当我们需要对请求进行预处理和后处理时,就可以用Flask提供的回调函数(钩子),他们可用来注册在请求处理的不同阶段执行的处理函数。这些请求钩子使用装饰器实现,通过程序实例app调用,以 before_request钩子为例(请求之前),当你对一个函数附加了app.before_request装饰器后,就会将这个函数注册为before_reque...

Python机器学习(6)——逻辑回归分类

在本系列文章中提到过用Python机器学习(2)数据拟合与广义线性回归中提到过回归算法来进行数值预测。逻辑回归算法本质还是回归,只是其引入了逻辑函数来帮助其分类。实践发现,逻辑回归在文本分类领域表现的也很优秀。现在让我们来一探究竟。 1、逻辑函数 假设数据集有n个独立的特征,x1到xn为样本的n个特征。常规的回归算法的目标是拟合出一个多项式函数,使得预测...

Python实例---基于页面的后台管理[简单版]

后台管理菜单 + 母板[css/content/js] 向后台提交数据[2种]: 1. 模态对话框(数据少操作,且Js复杂):form表单 :优点:简单,前端提交后后台处理完成后直接redirect;缺点:无法显示错误信息Ajax提交 : - 有错误,显示错误;无错误,通过js的 location.relad() 刷新页面- 有错误,显示错误;无错误,自...

Shell终端收听音乐--网易云音乐命令行版

Musicbox:网易云音乐命令行版本 高品质网易云音乐命令行版本,简洁优雅,丝般顺滑,基于Python编写。 这款命令行的客户端使用 Python 构建,以 mpg123 作为播放后端: Vim 式的流畅操作,支持快捷键绑定 支持电台、收藏等各种特色功能 支持 OS X 及各类 Linux 发行版 安装Python2.* pacman -S pytho...

python 获取图片并自动命名保存

# -* - coding: UTF-8 -* -#导入第三方库import urllibfrom bs4 import BeautifulSoupimport requestsimport osimport timeimport random# 获取文件夹,如果文件夹不存在则创建新文件夹if os.path.isdir('E://biaoqing//')...