Python:生成器表达式与产量

新手上路,请多包涵

在 Python 中,通过 生成器表达式 创建生成器对象与使用 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) 等。

一个或另一个的任何优点?想法?

原文由 cschol 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 695
2 个回答

两者只有细微差别。您可以使用 dis 模块自己检查这类事情。

编辑: 我的第一个版本在交互式提示中反编译了在模块范围内创建的生成器表达式。这与函数内部使用的 OP 版本略有不同。我修改了它以匹配问题中的实际情况。

正如您在下面看到的,“yield”生成器(第一种情况)在设置中有三个额外的指令,但是从第一个 FOR_ITER 它们只有一个方面不同:“yield”方法使用了一个 LOAD_FAST 代替循环内的 LOAD_DEREFLOAD_DEREFLOAD_FAST “相当慢” ,因此对于足够大的值 x (the外循环),因为 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

原文由 Peter Hansen 发布,翻译遵循 CC BY-SA 2.5 许可协议

在这个例子中,不是真的。但是 yield 可以用于更复杂的构造—— 例如,它也可以接受来自调用者的值并因此修改流程。阅读 PEP 342 了解更多详情(这是一项值得了解的有趣技术)。

无论如何,最好的建议是 使用更能满足您需求的方法

PS 这是 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!")

原文由 Eli Bendersky 发布,翻译遵循 CC BY-SA 2.5 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题