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

yield是python中一个很是重要的关键词,全部迭代器都是yield实现的,学习python,若是不把这个yield的意思和用法完全搞清楚,学习python的生成器,协程和异步io的时候,就会完全懵逼。因此写一篇总结讲讲yield的东西。html

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

一, 生成器中使用yield异步

语法形式:yield <表达式>函数

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

 1 def generator():  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 except StopIteration: 23     print('StopIteration n=%s' % n)          # 第17行的next将触发异常

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

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 code

二, 能够接收传入参数值的yield协程

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

next方式触发生成器没法传入参数, 若是想触发generator的同时, 传入参数给生成器,是否能够喃?答案是能够的, 这时候须要作两个改变,一是在生成器内增长变量接收该参数, 即本届标题形式;二是外部调用时, 不使用next, 而采用用调用生成器的send方法便可blog

 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 except StopIteration: 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 def generator(): 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循环

def generator(): 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) except StopIteration: 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之最详最强解释,一看就懂(二)

相关文章
相关标签/搜索