Python:生成器表达式与产量 [英] Python: generator expression vs. yield
问题描述
在Python中,通过 generator表达式创建生成器对象与使用 yield 语句之间有什么区别吗?
In Python, is there any difference between creating a generator object through a generator expression versus using the yield statement?
使用收益:
def Generator(x, y):
for i in xrange(x):
for j in xrange(y):
yield(i, j)
使用生成器表达式:
def Generator(x, y):
return ((i, j) for i in xrange(x) for j in xrange(y))
这两个函数都返回生成器对象,它们生成元组,例如(0,0),(0,1)等
Both functions return generator objects, which produce tuples, e.g. (0,0), (0,1) etc.
一个或另一个的任何优势?有想法吗?
Any advantages of one or the other? Thoughts?
谢谢大家!这些答案中有很多很棒的信息和进一步的参考!
Thanks everybody! There is a lot of great information and further references in these answers!
推荐答案
两者之间只有细微的差别.您可以使用dis
模块自己检查这种事情.
There are only slight differences in the two. You can use the dis
module to examine this sort of thing for yourself.
编辑:我的第一个版本在交互式提示中反编译了在模块范围内创建的生成器表达式.这与在函数内部使用的OP版本略有不同.我已经对此进行了修改,使其与问题中的实际情况相符.
My first version decompiled the generator expression created at module-scope in the interactive prompt. That's slightly different from the OP's version with it used inside a function. I've modified this to match the actual case in the question.
如下所示,"yield"生成器(第一种情况)在设置中具有三个额外的指令,但与第一个FOR_ITER
相比它们仅在一个方面有所不同:"yield"方法使用的是LOAD_FAST
代替循环内的LOAD_DEREF
. LOAD_DEREF
是慢得多" 比LOAD_FAST
,因此对于x
足够大的值(外部循环),它使"yield"版本比生成器表达式稍微快一点,因为y
的值在每次遍历中都稍快地加载.对于较小的x
值,由于设置代码的额外开销,它会稍微慢一些.
As you can see below, the "yield" generator (first case) has three extra instructions in the setup, but from the first FOR_ITER
they differ in only one respect: the "yield" approach uses a LOAD_FAST
in place of a LOAD_DEREF
inside the loop. The LOAD_DEREF
is "rather slower" than LOAD_FAST
, so it makes the "yield" version slightly faster than the generator expression for large enough values of x
(the outer loop) because the value of y
is loaded slightly faster on each pass. For smaller values of x
it would be slightly slower because of the extra overhead of the setup code.
也许值得指出的是,生成器表达式通常会在代码中内联使用,而不是像这样用函数包装它.这样会节省一些设置开销,并且对于较小的循环值,即使LOAD_FAST
为"yield"版本提供了优势,也可以使生成器表达式稍快一些.
It might also be worth pointing out that the generator expression would usually be used inline in the code, rather than wrapping it with the function like that. That would remove a bit of the setup overhead and keep the generator expression slightly faster for smaller loop values even if LOAD_FAST
gave the "yield" version an advantage otherwise.
在任何情况下,性能差异都不足以证明在一个或另一个之间做出决定是合理的.可读性的重要性要大得多,因此请使用对当前情况最易读的方式.
In neither case would the performance difference be enough to justify deciding between one or the other. Readability counts far more, so use whichever feels most readable for the situation at hand.
>>> def Generator(x, y):
... for i in xrange(x):
... for j in xrange(y):
... yield(i, j)
...
>>> dis.dis(Generator)
2 0 SETUP_LOOP 54 (to 57)
3 LOAD_GLOBAL 0 (xrange)
6 LOAD_FAST 0 (x)
9 CALL_FUNCTION 1
12 GET_ITER
>> 13 FOR_ITER 40 (to 56)
16 STORE_FAST 2 (i)
3 19 SETUP_LOOP 31 (to 53)
22 LOAD_GLOBAL 0 (xrange)
25 LOAD_FAST 1 (y)
28 CALL_FUNCTION 1
31 GET_ITER
>> 32 FOR_ITER 17 (to 52)
35 STORE_FAST 3 (j)
4 38 LOAD_FAST 2 (i)
41 LOAD_FAST 3 (j)
44 BUILD_TUPLE 2
47 YIELD_VALUE
48 POP_TOP
49 JUMP_ABSOLUTE 32
>> 52 POP_BLOCK
>> 53 JUMP_ABSOLUTE 13
>> 56 POP_BLOCK
>> 57 LOAD_CONST 0 (None)
60 RETURN_VALUE
>>> def Generator_expr(x, y):
... return ((i, j) for i in xrange(x) for j in xrange(y))
...
>>> dis.dis(Generator_expr.func_code.co_consts[1])
2 0 SETUP_LOOP 47 (to 50)
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 40 (to 49)
9 STORE_FAST 1 (i)
12 SETUP_LOOP 31 (to 46)
15 LOAD_GLOBAL 0 (xrange)
18 LOAD_DEREF 0 (y)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 17 (to 45)
28 STORE_FAST 2 (j)
31 LOAD_FAST 1 (i)
34 LOAD_FAST 2 (j)
37 BUILD_TUPLE 2
40 YIELD_VALUE
41 POP_TOP
42 JUMP_ABSOLUTE 25
>> 45 POP_BLOCK
>> 46 JUMP_ABSOLUTE 6
>> 49 POP_BLOCK
>> 50 LOAD_CONST 0 (None)
53 RETURN_VALUE
这篇关于Python:生成器表达式与产量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!