美文网首页虫虫
【python】中的 yield from 句法

【python】中的 yield from 句法

作者: Alcazar | 来源:发表于2019-07-04 20:36 被阅读38次
    yield from 句法

    在生成器中使用yield from subgen()时,subgen会获得控制权,把产出的值传给生成器的调用方,即调用方可以直接控制subgen。与此同时,gen会阻塞,等待subgen终止。

    【简单应用】用来简化for循环中的yield表达式

    举个栗子:

    def gen():
        for c in 'AB':
            yield c
    
        for i in range(1,3):
            yield i
    
    print(list(gen()))
    

    前后对比

    def gen():
        yield from 'AB'
        yield from range(1,3)
        
    print(list(gen()))
    

    【解释】yield from x表达式对x对象所做的第一件事是,调用iter(x),从中获取迭代器。因此,x可以是任何可迭代的对象。


    【应用升级】把职责委托给子生成器

    yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。通过这个结构,协程可以把功能委托给子生成器。

    主要术语:

    • 委派生成器:
      包含 yield from <iterable> 表达式的生成器函数。

    • 子生成器:
      从 yield from 表达式中 <iterable>部分获取的生成器函数。

    • 调用方
      调用委派生成器的客户端(调用方)代码。

    graph LR
    调用方 ==> 委派生成器
    委派生成器 ==> 子生成器

    【店小二】:各位看官,下面的代码请查收@ @

    from collections import namedtuple
    
    Result = namedtuple('Result','count average')
    
    # 子生成器
    def averager():
        total = 0.0
        count = 0
        average = None
    
        while True:
            # main 方法中发送的各种值,会绑定到term变量上
            term = yield
            
            # 子生成器终止的条件
            if term is None:
                break
    
            total += term
            count += 1
            average = total / count
        
        # 返回值会成为grouper中 yield from表达式的值
        return Result(count,average)
    
    # 委派生成器
    def grouper(results,key):
        while True:
            # 每次迭代都会生成一个averager实例。每个生成器都是本协程(grouper)使用的生成器对象。
            results[key] = yield from averager()
    
    # 客户端代码
    def main(data):
        results = {}
        for key,values in data.items():
            # results 用来存储结果
            group = grouper(results, key)
            # 预激活协程
            next(group)
            for value in values:
                # 发送的每个值都会经由grouper的yield from处理,通过管道传给averager实例。同时,当前的grouper实例,会在yield from 处暂停。
                group.send(value)
            # 把None值传入grouper,导致当前的averager实例终止,并让grouper继续运行,再创建一个aveager实例,处理下一组值。
            group.send(None)
        print(results)
    
    data = {
        'girls;kg':[40.9,38.5,44.3,42.2,45.2,41.7,44.5,38.0,40.6,44.5],
        'girls;m':[1.6,1.51,1.4,1.3,1.41,1.39,1.33,1.46,1.45,1.43],
        'boys;kg':[39.0,40.8,43.2,40.8,43.1,38.6,41.4,40.6,36.3],
        'boys;m':[1.38,1.5,1.32,1.25,1.37,1.48,1.25,1.49,1.46]
    }
    
    
    main(data)
    

    Output:
    {'girls;kg': Result(count=10, average=42.040000000000006), 'girls;m': Result(count=10, average=1.4279999999999997), 'boys;kg': Result(count=9, average=40.422222222222224), 'boys;m': Result(count=9, average=1.3888888888888888)}

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


    【重点】

    yield from 句法的注意点
    • 如果子生成器不终止,委派生成器会在yield from处永远暂停。

    • 因为委派生成器相当于管道,所以可以把任意数量个委派生成器连接在一起:一个委派生成器使用yield from调用一个子生成器,而那个子生成器本身也是委派生成器,使用yield from调用另一个子生成器,以此类推。最终,这个链条要以一个只使用yield表达式的简单生成器结束;不过,也能以任何可迭代的对象结束。

    • 任何yield from链条都必须由客户驱动,在最外层委派生成器上调用next(...)函数或.send(...)方法。

    yield from 句法的意义

    【写在前面】下面的几点知识...有点复杂,考虑的如果不够全面的话,可能写的程序会崩。

    • 子生成器产出的值都直接传给委派生成器的调用方(即客户端代码)。

    • 使用send()方法发给委派生成器的值都直接传给子生成器。如果发送的值是None,那么会调用子生成器的next()方法。

    如果发送的值不是None,那么会调用子生成器的send()方法。如果调用的方法抛出StopIteration异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。

    • 生成器退出时,生成器(或子生成器)中的return expr表达式会触发StopIteration(expr)异常抛出。

    • yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数。

    • 传入委派生成器的异常,除了GeneratorExit之外都传给子生成器的throw()方法。

    如果调用throw()方法时抛出StopIteration异常,委派生成器恢复运行。StopIteration之外的异常会向上冒泡,传给委派生成器。

    • 如果把GeneratorExit异常传入委派生成器,或者在委派生成器上调用close()方法,那么在子生成器上调用close()方法,如果它有的话。如果调用close()方法导致异常抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出GeneratorExit异常。

    相关文章

      网友评论

        本文标题:【python】中的 yield from 句法

        本文链接:https://www.haomeiwen.com/subject/ikrdhctx.html