详解 Python 中的 yield from 语法

详解 Python 中的 yield from 语法

转载请注明出处:https://blog.csdn.net/jpch89/article/details/87776528



0. 参考资料


1. 语法和语义描述

yield from <expr> 这种表达式语法可以出现在一个生成器内部。
其中 <expr> 必须是一个可迭代对象,我们从中获取迭代器。
迭代器会运行直至元素耗尽,在此过程中,它直接从包含 yield from 表达式的生成器(委派生成器 delegating generator)的调用方 caller 获取值,并向其生成值。
此外,当该迭代器是另一个生成器的时候,子生成器可以执行 return 语句,该语句的值会成为 yield from 表达式的值。

yield from 表达式的完整语义如下:

  • 迭代器生成的任何值都会直接传给调用方
  • 通过 send 发送给委派生成器的值会直接传给迭代器。如果发送的值为 None,会调用迭代器的 __next__() 方法。如果发送的值不是 None,会调用迭代器的 send() 方法。如果调用时抛出了 StopIteration 异常,委派生成器恢复运行。任何其他的异常会向上传播给委派生成器。
  • throw 给委派生成器的异常中,除了 GeneratorExit 异常,都会传递给迭代器的 throw() 方法。如果调用抛出 StopIteration,委派生成器恢复运行。任何其他的异常会向上传播给委派生成器。
  • 如果向委派生成器 throwGeneratorExit 异常,或者调用了委派生成器的 close() 方法,则会调用迭代器的 close() 方法(如果它有的话)。如果调用导致异常,它会向上传播给委派生成器。否则,委派生成器自身抛出 GeneratorExit 异常。
  • yield from 表达式的值是迭代器终止时抛出的 StopIteration 异常的第一个参数。
  • 生成器中的 return expr 会在生成器退出的时候抛出 StopIteration(expr) 异常。

2. 扩展了 StopIteration

为了方便,StopIteration 异常被赋予一个 value 属性,它会存储 StopIteration 的第一个参数,如果没有参数,则为 None


3. 等价代码

  1. RESULT = yield from EXPR
    变量说明:
  • _i 子生成器
  • _y 子生成器产出的值
  • _r 最终结果,即子生成器运行结束后 yield from 表达式的值
  • _s 发送的值,调用方发给委派生成器的值,这个值会转发给子生成器
  • _e 异常对象

RESULT = yield from EXPR 在语义上与下面的代码等价:

# EXPR 是可迭代对象,使用 iter() 来获取迭代器
_i = iter(EXPR)
try:
    # yield from 会自动预激子生成器,结果保存在 _y,作为产出的第一个值
    # 如果 _i 仅仅是一个迭代器的话,那么只是产出第一个值
    _y = next(_i)
except StopIteration as _e:
    # 如果这一步抛出 StopIteration,把异常对象的 value 赋值给返回值 _r
    _r = _e.value
else:
    while 1:
        try:
            # 产出迭代器当前产出的元素;等待调用方发送 _s 中保存的值
            _s = yield _y
        # 当 throw 给委派生成器 GeneratorExit 异常
        # 或者调用委派生成器的 close() 方法时
        # 会进一步调用迭代器的 close() 方法
        except GeneratorExit as _e:
            try:
                _m = _i.close
            except AttributeError:
                # 如果仅仅是一个没有 close() 方法的迭代器则什么都不做
                pass
            else:
                _m()
            # 关闭完子生成器之后,在委派生成器中继续抛出 GeneratorExit
            raise _e
        # 处理调用方通过 .throw(...) 方法传入的异常
        except BaseException as _e:
            _x = sys.exc_info()
            # 如果 _i 是一个没有 throw 方法的迭代器
            # 那么委派生成器会抛出调用方 throw 进来的异常
            try:
                _m = _i.throw
            except AttributeError:
                raise _e
            # 如果有 throw 方法,那么调用子生成器的 throw
            else:
                try:
                    _y = _m(*_x)
                # 遇到子生成器的 StopIteration 不会向上传播
                # 而是会取值赋给 RESULT
                except StopIteration as _e:
                    _r = _e.value
                    break
        # 生成值的过程中没有异常发生
        else:
            try:
                # 如果调用方发送过来的值是 None 则 next(迭代器)
                if _s is None:
                    _y = next(_i)
                # 否则把发送过来的值转交给迭代器 _i
                else:
                    _y = _i.send(_s)
            # 如果出现 StopIteration 异常,把 value 给 _r 并退出循环
            except StopIteration as _e:
                _r = _e.value
                break
RESULT = _r
  1. 在生成器中,return value 语句等价于 raise StopIteration(value)
    除了一点不同,目前这样的异常没有办法在返回的生成器中使用 except 捕获。

  2. StopIteration 的行为就好像有着如下定义:

class StopIteration(Exception):

    def __init__(self, *args):
        if len(args) > 0:
            self.value = args[0]
        else:
            self.value = None
        Exception.__init__(self, *args)

4. 重构注意事项

  • 如果一个代码块捕获 GeneratorExit 异常且没有重新抛出该异常,那么这段代码无法在完全保留其原始行为的情况下分离出去。
  • 如果向委派生成器 throwStopIteration 异常,分离出去的代码可能不会像原始代码一样工作。

注意:这些用例几乎不存在。


5. 图解分析

yield from

上图有三个关键组成部分:

  • 上游调用方 upstream caller
  • 委派生成器 delegating generator
  • 下游生成器 downstream generator

上游调用方的形式有两种:

  • 普通函数形式:
    使用 for 或者 next
  • 生成器形式(意味着多个委派生成器可以链式嵌套):
    使用 yield from

其实下游生成器不一定是一个生成器,还可以是:

  • 可迭代对象(比如列表)
    此时它只能生成值,不能使用其它高级特性,比如 send 方法和 throw 方法。
  • 任意实现了 __next__()send()throw()close() 方法的对象,或者实现了它们的子集。此时该对象会被当成一个生成器来使用,这也是 Python 中鸭子类型/多态的体现。

委派生成器中 yield from 的作用可以形象地理解为 在上游调用方和下游生成器之间建立了一个可以双向传值的透明管道。透明管道的一端是上游调用方,另一端是下游生成器,而通过透明管道传递的任何东西,虽然要经由委派生成器来调用或者传递,但是它们对委派生成器来说都是不可见的。

具体来说,通过透明管道传输的数据有以下几种:

  • 上游调用方通过 send 方法向委派生成器下游生成器传递的任何值
  • 下游生成器通过 yield 方法向上游调用方生成的任何值
  • 上游调用方通过 throw 方法向下游生成器抛出的异常
    注意:如果抛出的是 GeneratorExit 异常,那么会调用下游生成器的 close() 方法(如果有的话),然后在委派生成器中继续抛出 GeneratorExit 异常
  • 上游调用方调用 close() 方法会直接调用下游生成器的 close() 方法(如果有的话),随后在委派生成器中继续抛出 GeneratorExit 异常。

从下游到上游的异常则不经由透明管道传输:

  • 下游生成器产生的 StopIteration 异常会被委派生成器拦截并处理。
    处理方式是把 StopIteration 异常对象的 value 属性赋值给整个 yield from 表达式。
  • 下游生成器产生的其他异常,则向上传播给委派生成器,如果委派生成器未处理,则继续上传给上游调用方。

完成于 2019.02.20

猜你喜欢

转载自blog.csdn.net/jpch89/article/details/87776528