0x02 yield from与生成器

yield from

yield from使用在生成器的定义过程中, 与yield对比, yield from subgen()这种形式, 可以从子生成器中获取结果, 更准确的说是子生成器subgen会获得控制权, 并且把产出的值直接传给的调用方, 而不再经过生成器, 这种形式:

  • 方便了生成器的嵌套使用

  • 调用方可以直接调用子生成器

在子生成器执行时, 生成器gen会阻塞, 等待子生成器subgen交换控制权.

事实上, yield from会调用紧跟对象的__iter__方法, 因此对于任何可迭代的对象, 如str, list等都可以配合yield from使用, 代替繁琐的循环:

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

list(gen())

输出为:

['A', 'B', '1', '2']

执行过程

对主函数直接调用的生成器, 以及上文中的子生成器, 正式定义为:

  • 调用方: 调用委派生成器的客户端代码

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

  • 子生成器: 从yield from部分获取的生成器

三者是一种层级递进关系.

yield from的主要功能是打开双向通道, 把最外层的调用方与最内层的子生成器连接起来, 使两者可以直接发送和产出值, 还可以直接传入异常, 而不用在中间的协程添加异常处理的代码.

委派生成器在yield from表达式处暂停时, 调用方可以直接把数据发给子生成器, 子生成器再把产出的值发送给调用方.

子生成器返回之后, 解释器会抛出StopIteration异常, 并把返回值附加到异常对象上, 委派生成器恢复.

从一个例子中感受yield from的执行过程.

执行结果为:

上面的代码展示了yield from的基础用法, 委派生成器相当于管道, 所以可以把任意数量的委派生成器连接在一起. 而且委派生成器中的子生成器内部也可以调用生成器, 从而加深了调用关系, 只需要最终以一个只是用yield表达式的生成器结束.

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协程2:yield from 分析

python协程3:用仿真实验学习协程

最后更新于

这有帮助吗?