详解 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
,委派生成器恢复运行。任何其他的异常会向上传播给委派生成器。- 如果向委派生成器
throw
了GeneratorExit
异常,或者调用了委派生成器的close()
方法,则会调用迭代器的close()
方法(如果它有的话)。如果调用导致异常,它会向上传播给委派生成器。否则,委派生成器自身抛出GeneratorExit
异常。 yield from
表达式的值是迭代器终止时抛出的StopIteration
异常的第一个参数。- 生成器中的
return expr
会在生成器退出的时候抛出StopIteration(expr)
异常。
2. 扩展了 StopIteration
为了方便,StopIteration
异常被赋予一个 value
属性,它会存储 StopIteration
的第一个参数,如果没有参数,则为 None
。
3. 等价代码
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
-
在生成器中,
return value
语句等价于raise StopIteration(value)
。
除了一点不同,目前这样的异常没有办法在返回的生成器中使用except
捕获。 -
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
异常且没有重新抛出该异常,那么这段代码无法在完全保留其原始行为的情况下分离出去。 - 如果向委派生成器
throw
了StopIteration
异常,分离出去的代码可能不会像原始代码一样工作。
注意:这些用例几乎不存在。
5. 图解分析
上图有三个关键组成部分:
- 上游调用方
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