Python:为什么一个生成器比另一个里面有 `yield` 的生成器快?

标签 python python-3.x performance generator bytecode

我有两个函数返回生成器:

def f1():
    return (i for i in range(1000))

def f2():
    return ((yield i) for i in range(1000))

显然,从 f2() 返回的生成器比 f1() 慢两倍:

Python 3.6.5 (default, Apr  1 2018, 05:46:30) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit, dis
>>> timeit.timeit("list(f1())", globals=globals(), number=1000)
0.057948426001530606
>>> timeit.timeit("list(f2())", globals=globals(), number=1000)
0.09769760200288147

我尝试使用 dis 查看发生了什么但无济于事:

>>> dis.dis(f1)
  2           0 LOAD_CONST               1 (<code object <genexpr> at 0x7ffff7ec6d20, file "<stdin>", line 2>)
              2 LOAD_CONST               2 ('f1.<locals>.<genexpr>')
              4 MAKE_FUNCTION            0
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               3 (1000)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE
>>> dis.dis(f2)
  2           0 LOAD_CONST               1 (<code object <genexpr> at 0x7ffff67a25d0, file "<stdin>", line 2>)
              2 LOAD_CONST               2 ('f2.<locals>.<genexpr>')
              4 MAKE_FUNCTION            0
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               3 (1000)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

显然,dis 的结果是相同的。

那么为什么从 f1() 返回的生成器比从 f2() 返回的生成器快?调试这个的正确方法是什么?显然 dis 在这种情况下失败了。

编辑 1:

及时使用 next() 而不是 list() 会反转结果(或者在某些情况下它们是相同的):

>>> timeit.timeit("next(f1())", globals=globals(), number=10**6)
1.0030477920008707
>>> timeit.timeit("next(f2())", globals=globals(), number=10**6)
0.9416838550023385

编辑 2:

显然这是 Python 中的错误,已在 3.8 中修复。参见 yield in list comprehensions and generator expressions

内部带有 yield 的生成器实际上会产生两个值。

最佳答案

生成器表达式中的 Yield 实际上是一个错误,如 this related question 中所述.

如果你想真正了解 dis 发生了什么,你需要内省(introspection)代码对象的 co_const[0],所以:

>>> dis.dis(f1.__code__.co_consts[1])
  2           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
>>> dis.dis(f2.__code__.co_consts[1])
  2           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                12 (to 18)
              6 STORE_FAST               1 (i)
              9 LOAD_FAST                1 (i)
             12 YIELD_VALUE
             13 YIELD_VALUE
             14 POP_TOP
             15 JUMP_ABSOLUTE            3
        >>   18 LOAD_CONST               0 (None)
             21 RETURN_VALUE

所以,它产生了两倍。

关于Python:为什么一个生成器比另一个里面有 `yield` 的生成器快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51281236/

相关文章:

python - 计数问题: possible sudoko tables?

python - 将水平存储的数据转置为多行

regex - 如何将 map 与具有正则表达式键的字典一起使用?

python - 运行时错误 : Attempting to deserialize object on a CUDA device

mysql - 如何加快 MySQL 表连接

sql - 选择最近点的最有效方法

python - 在 Python 中对字典实现 for 循环

python - PySide2 拖放功能不起作用

Python 使用 Pandas/Urllib 下载文件

java - Java 如何在非常小的设备上运行?