您提供的函数在一般情况下具有完全不同的语义。
第一个,yield from,将控制权传递给可迭代对象。这意味着在迭代期间对 send() 和 throw() 的调用将由可迭代对象处理,而不是由您定义的函数处理。
第二个函数只对可迭代对象的元素进行迭代,它将处理对send() 和throw() 的所有调用。要查看差异,请检查以下代码:
In [8]: def action():
...: try:
...: for el in range(4):
...: yield el
...: except ValueError:
...: yield -1
...:
In [9]: def f():
...: yield from action()
...:
In [10]: def g():
...: return (el for el in action())
...:
In [11]: x = f()
In [12]: next(x)
Out[12]: 0
In [13]: x.throw(ValueError())
Out[13]: -1
In [14]: next(x)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-14-5e4e57af3a97> in <module>()
----> 1 next(x)
StopIteration:
In [15]: x = g()
In [16]: next(x)
Out[16]: 0
In [17]: x.throw(ValueError())
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-17-1006c792356f> in <module>()
----> 1 x.throw(ValueError())
<ipython-input-10-f156e9011f2f> in <genexpr>(.0)
1 def g():
----> 2 return (el for el in action())
3
ValueError:
事实上,由于这个原因,yield from 的开销可能比 genexp 更高,尽管它可能无关紧要。
仅当上述行为是您想要的或时才使用yield from,如果您正在迭代一个不是生成器的简单可迭代对象(因此yield from相当于一个循环+简单yields)。
从风格上来说我更喜欢:
def h():
for el in range(4):
yield el
而不是 return 使用 genexp 或使用 yield from 在处理生成器时。
实际上,生成器用来执行迭代的代码与上面的函数几乎相同:
In [22]: dis.dis((i for i in range(4)).gi_code)
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 11 (to 17)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 POP_TOP
14 JUMP_ABSOLUTE 3
>> 17 LOAD_CONST 0 (None)
20 RETURN_VALUE
如您所见,它执行FOR_ITER + YIELD_VALUE。请注意,参数 (.0) 是 iter(range(4))。该函数的字节码还包含对LOAD_GLOBAL 和GET_ITER 的调用,这些调用是查找range 并获取其可迭代对象所需的。然而,这个动作也必须由 genexp 执行,只是不是在它的代码中,而是在调用它之前。