Python:生成器表达式与yield

98 投票
8 回答
33615 浏览
提问于 2025-04-15 17:35

在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 个回答

19

对于那些简单的循环,你可以用生成器表达式来处理,它们没有什么区别。不过,使用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]
37

在这个例子中,其实并没有太多复杂的东西。不过,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!")
79

这两者之间的区别其实很小。你可以使用 dis 模块自己来查看这些内容。

编辑:我最开始的版本是解码在交互提示中创建的模块级生成器表达式。这和提问者在函数中使用的版本有点不同。我已经修改过来,使其与问题中的实际情况相符。

如你所见,"yield" 生成器(第一种情况)在设置时多了三条指令,但从第一个 FOR_ITER 开始,它们只有一个不同之处:在循环中,"yield" 的方法使用了 LOAD_FAST,而不是 LOAD_DEREFLOAD_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

撰写回答