你提供的函数在一般情况下语义完全不同。
第一个函数使用“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”)时,才使用“yield from”。
从风格上讲,我更喜欢:
def h():
for el in range(4):
yield el
在处理生成器时,不要使用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
并获取其可迭代对象。然而,这些操作也必须由生成器表达式执行,只不过不在其代码内部,而是在调用之前执行。