• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

python协程系列(三)——yield from原理详解

开发技术 开发技术 5小时前 1次浏览

  参考:https://blog.csdn.net/qq_27825451/article/details/85244237

  一,yield from的简单实现

  从前面系列文章中,我们了解到,yiedl每次“惰性返回”一个值,其实从名字中就能看出,yield from是yield的升级改进版本,如果将yield理解成“返回”,那么yield from就是从“从什么(生成器)里面返回”,这就构成了yield from的一般语法,即

yield  from generator

  这样的形式。我们通过一个简单的例子来看

  use_yield_from.py

# 生成器依次生成0 1 2 3 4 5 6 7 8 9
def generator1():
    for i in range(10):
        yield i

# 演示yield from的用法
def generator2():
    yield 'a'
    yield 'b'
    yield 'c'
    yield from generator1()
    # for item in generator1():
    #     yield item
    yield from [11,22,33,44]
    yield from (11,23,34)
    yield from range(3)

for i in generator2():
    print(i, end=' , ')
# a , b , c , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 11 , 22 , 33 , 44 , 11 , 23 , 34 , 0 , 1 , 2 , 

  总结

  从上面代码输出可以看出,yield from后面可以是”生成器,元组,列表,range()函数产生的序列等可迭代对象”

  简单地说,yield from generator。实际上就是返回另外一个生成器。而yield只返回一个元素(即假如yield后面是一个list则返回这个list而不是list里面的元素)。从这个层面来说,有下面的等价关系

yield from iterable
# 等价于
for item in Iterable:
    yield item

  下面例子演示yield和yield from返回的不同

def g():
    yield [1,2,3]
    yield from [1,2,3]

g = g()
for item in g:
    print(item)

  输出如下

[1, 2, 3]
1
2
3

  解析:使用yield则把整个list作为一个元素返回了,使用yield则把list作为一个可迭代对象把内部的对象一一返回

  二,yiedl from的高级应用

  倒入,yield from既然称之为yield的升级改进版,如果仅仅是上面的哪一点作用,显然是不够的,因为那仅仅简化了一两句代码的事情。系列文章的上一篇讲解了yield所存在的缺点,参见上一篇文章:https://www.cnblogs.com/minseo/p/15396480.html

  yield from正是针对那些不足加以改进的。

  1,针对yield无法获取生成器return的返回值

  我们都知道,在使用yield生成器的时候,如果使用for语句去迭代生成器,则不会显式的出发StopIteration异常,而是自动捕获StopIteration异常,所以如果遇到return,只是会终止迭代,而不会触发异常,故而也就没办法获取return的值。如下:

# # 使用for循环遇到生成器StopIteration异常不会抛出异常只会终止迭代,没有办法获得return值
def my_generator():
    for i in range(5):
        if i==2:
            return '我被迫中断了'
        else:
            yield i
 
def main(generator):
    try:
        #不会显式触发异常,故而无法获取到return的值
        for i in generator:  
            print(i)
    except StopIteration as exc:
        print(exc.value)

# 调用
g=my_generator()  
main(g)
# 0
# 1

  说明:因为使用for迭代遇到StopIteration异常也不会抛出异常,而是终止迭代,所以except永远无法捕获到StopIteration异常,所以也无法取得生成器的return值

  从上面的例子可以看出,for迭代语句不会显式触发异常,故而无法获取到return的值,迭代到2的时候遇到return语句,隐式的触发了StopIteration异常,就终止迭代了,但是在程序中不会显示出来。

  但是如果我是使用next(g)一次一次迭代,则会显式触发异常,但要获取return的返回值,我需要如下操作:

# 使用next方法才可以捕获到StopIteration异常从而获得return值
def my_generator():
    for i in range(5):
        if i==2:
            return '我被迫中断了'
        else:
            yield i
 
def main(generator):
    try:
        # 使用next方法每次迭代一个值,则会显式触发StopIteration
        print(next(generator))   
        print(next(generator))
        # 迭代两次以后i值为2则遇到return生成器抛出StopIteration异常
        print(next(generator))
        # 因为生成器return了触发了StopIteration异常,所以以下语句不执行
        print(next(generator))
        print(next(generator))
    except StopIteration as exc:
        # 获取返回的值
        print(exc.value)     
 
g=my_generator()
main(g)
# 0
# 1
# 我被迫中断了

  执行两次迭代以后i的值为2,所以生成器return抛出StopIteration异常,异常被捕获以后可以通过异常的value属性获得生成器的return值

  现在我们使用yield from来完成上面的同样的功能:

# 使用yield from获得return值
def my_generator():
    for i in range(5):
        if i==2:
            return '我被迫中断了'
        else:
            yield i

# 定义一个包装“生成器”,它的本质还是生成器
def wrap_my_grnerator(generator):
    # 自动触发StopIteration异常,并且将return的返回值赋值给yield from表达式的结果,即热塑了
    result = yield from generator
    print(result)

# 调用主函数,调用的生成器是包装生成器而不是原始生成器
def main(generator):
    for i in generator:
        print(i)

g = my_generator()
wrap_g = wrap_my_grnerator(g)
# 调用
main(wrap_g)

  运行结果如下

0
1
我被迫中断了

  解析:

  main为主调用函数,调用的生成器为包装生成器wrap_my_generator该包装生成器内部调用的是原始生成器my_generstor,使用yield from可以获得生成器的return值。

  从上面的比较可以看出,yield from具有以下几个特点

  (1) 上面的my_grnerator是原始生成器,main是调用方,使用yield的时候,值涉及到这两个函数,即“调用方”于“生成器(协程函数)”是直接进行交互的,不涉及其他方法,即“调用方——->生成器(协程函数)”;

    (2) 在使用yield from的时候,多了一个队原始my_generstor的包装函数,然后调研方通过这个包装函数(后面会讲到它的专有名词)来于生成器进行交互,即“调研方——>生成器包装函数——>生成器(协程函数)”

  (3) yield from iteration结构会在内部自动捕获iteration生成器的StopIteration异常。这种处理方式与for循环处理StopIteration异常的方式一样。而且对yield from结构来说,解释器不仅会捕获StopIteration异常,还会把return返回值或者是StopIteration的value属性变成yield from表达式的值,即上面的result。

  2,yiedl from实现的数据传输通道

  前面总结的几个特点里面已经介绍了yield和yield from的数据交互方式,yield涉及到“调用方与生成器两者”的交互,生成器通过next()的调用将值返回给调用者,而调用者通过send()方法向生成器发送数据;

  但是yield还有一个第三者函数,下面将先从相关的概念说起。

  在PEP 380 使用了一些yield from使用的专门术语:

  委派生成器:包含 yield from <iterable> 表达式的生成器函数;即上面的wrap_my_generator生成器函数

  子生成器:从 yield from 表达式中 <iterable> 部分获取的生成器;即上面的my_generator生成器函数

  调用方:调用委派生成器的客户端代码;即上面的main生成器函数

  下图是这三者之间的交互关系(摘自博客园):

python协程系列(三)——yield from原理详解

 

 

   委派生成器在yield from表达式出暂停时,调用可以直接把数据发给子生成器,子生成器再把产出的值发给调用方。子生成器返回后,解释器会抛出StopIteration异常,并把返回值附加到异常对象上,此时委派生成器会恢复。

  总结

  (1)yield from主要设计用来向子生成器委派操作任务,但yield from可以向任意的可迭代对象委派操作;

  (2)委派生成器(group)相当于管道,所以可以把任意数量的委派生成器连接在一起。一个委派生成器使用yield  from调用一个子生成器,而那个子生成器本身也是委派生成器,使用yield from调用另一个生成器。

  3,针对yield存在的第二个缺点

  首先看一下他要表述的意思是什么?它的局限性在于只能向它的直接调用者每次yield一个值。这意味着那些包含yield的代码不能像其他代码那样被分离出来放到一个单独的函数中。这也正是yield from要解决的。具体参见上文:

  https://www.cnblogs.com/minseo/p/15396480.html

  这句话确实难以理解,但是他要表达的意思实际上是:因为生成器从定义上来看,就像是一个普通的函数,那么既然作为普通函数,就应该可以反反复复调用都没问题的,但是生成器却并不行。那为什么yield from可以解决这样的问题呢,主要是因为yield from后面可以跟任意一个生成器,即yield from可以将任意的任务为派给任意生成器函数,从而避免了子生成器直接向调用者返回单个值的情况

  三,yiedl from用法示例

  其实yield from最重要的作用就是提供了一个“数据传输管道”,下面通过一个简单的例子加以说明为什么是管道:

  例子说明yield from的管道功能.py

def average():
    # 数字的总和
    total = 0.0
    # 数字的个数
    count = 0
    # 数字的平均值
    avg = None
    while True:
        # 平均值作为生成器的返回
        n = yield avg
        # 没跌倒一次生成器数字传递的数字个数+1
        count = count + 1
        # 计算传递数字的和
        total = total + n
        # 计算平均值
        avg = total / count
        
def wrap_average(generator):
    yield from generator

def main(wrap):
    # 启动生成器
    print(next(wrap))
    print(warp.send(10))
    print(warp.send(20))
    print(warp.send(30))
    print(warp.send(40))

g = average()
warp = wrap_average(g)
main(warp)
# None
# 10.0
# 15.0
# 20.0
# 25.0

  从上面我们可以发现,调研费发送数据是发给wrap_average的,怎么依然到了生成器函数avarage里面呢?这就是“数据传输管道的作用”。即主函数调用方main把各个value传给grouper,而这个传入的值最终达到average函数中;grouper并不知道传入是是什么值,因为从上面的代码看出,wrap_average里面完全没有处理这个值的任何代码 !

  现在是不是对“数据传输管道”更加理解了呢?

  使用调试模式更加深入理解委派生成器的管道功能

python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

 python协程系列(三)——yield from原理详解

 

   省略几步重复步骤…

python协程系列(三)——yield from原理详解

 

 

 

 

 
 


程序员灯塔
转载请注明原文链接:python协程系列(三)——yield from原理详解
喜欢 (0)