Python:生成器表达式与yield
在Python中,通过生成器表达式创建生成器对象和使用yield语句创建生成器对象有什么区别吗?
使用yield:
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)等等。
这两种方法各有什么优缺点呢?大家有什么想法?
8 个回答
对于那些简单的循环,你可以用生成器表达式来处理,它们没有什么区别。不过,使用yield可以创建出功能更复杂的生成器。下面是一个生成斐波那契数列的简单例子:
>>> def fibgen():
... a = b = 1
... while True:
... yield a
... a, b = b, a+b
>>> list(itertools.takewhile((lambda x: x<100), fibgen()))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
在这个例子中,其实并没有太多复杂的东西。不过,yield
可以用在更复杂的结构中——比如说,它可以接收调用者传来的值,并因此改变执行的流程。想了解更多,可以看看PEP 342,这是一个值得了解的有趣技巧。
总之,最好的建议就是使用对你需求来说更清晰的方式。
附带说明,这里有一个来自Dave Beazley的简单协程示例:
def grep(pattern):
print "Looking for %s" % pattern
while True:
line = (yield)
if pattern in line:
print line,
# Example use
if __name__ == '__main__':
g = grep("python")
g.next()
g.send("Yeah, but no, but yeah, but no")
g.send("A series of tubes")
g.send("python generators rock!")
这两者之间的区别其实很小。你可以使用 dis
模块自己来查看这些内容。
编辑:我最开始的版本是解码在交互提示中创建的模块级生成器表达式。这和提问者在函数中使用的版本有点不同。我已经修改过来,使其与问题中的实际情况相符。
如你所见,"yield" 生成器(第一种情况)在设置时多了三条指令,但从第一个 FOR_ITER
开始,它们只有一个不同之处:在循环中,"yield" 的方法使用了 LOAD_FAST
,而不是 LOAD_DEREF
。LOAD_DEREF
的速度 “相对较慢”,所以在 x
(外层循环)足够大的情况下,"yield" 版本会比生成器表达式稍微快一点,因为每次循环中 y
的值加载得更快。对于较小的 x
值来说,由于额外的设置代码,它会稍微慢一些。
还值得指出的是,生成器表达式通常会直接在代码中使用,而不是像这样用函数包裹起来。这样可以减少一些设置的开销,即使 LOAD_FAST
给 "yield" 版本带来了优势,生成器表达式在较小的循环值下也会稍微快一些。
在这两种情况下,性能差异都不足以让你在两者之间做出选择。可读性更重要,所以在具体情况下使用你觉得更易读的那种。
>>> 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