列表是否理解为Python 3中的`list(generator expression)`的语法糖?

zeh*_*ard 33 python list-comprehension generator-expression python-3.x python-internals

在Python 3中,列表理解只是语法糖,用于生成list函数的生成器表达式?

例如是以下代码:

squares = [x**2 for x in range(1000)]
Run Code Online (Sandbox Code Playgroud)

实际上在后台转换成以下内容?

squares = list(x**2 for x in range(1000))
Run Code Online (Sandbox Code Playgroud)

我知道输出是相同的,并且Python 3修复了列表推导所具有的周围命名空间的令人惊讶的副作用,但就CPython解释器所做的事情而言,前者转换为后者,或者是否有任何区别在如何执行代码?

背景

我发现,在评论部分等价的这一主张这个问题,和快速谷歌搜索显示了同样的要求正在作出这里.

在Python 3.0文档中的新内容中也提到了这一点,但措辞有些含糊:

还要注意,列表推导具有不同的语义:它们更接近于list()构造函数中的生成器表达式的语法糖,特别是循环控制变量不再泄漏到周围的范围中.

Ash*_*ary 31

两者的工作方式不同,列表推导版本利用特殊的字节码LIST_APPEND,直接为我们调用PyList_Append.因此,它避免list.append了Python级别的属性查找和函数调用.

>>> def func_lc():
    [x**2 for x in y]
...
>>> dis.dis(func_lc)
  2           0 LOAD_CONST               1 (<code object <listcomp> at 0x10d3c6780, file "<ipython-input-42-ead395105775>", line 2>)
              3 LOAD_CONST               2 ('func_lc.<locals>.<listcomp>')
              6 MAKE_FUNCTION            0
              9 LOAD_GLOBAL              0 (y)
             12 GET_ITER
             13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             16 POP_TOP
             17 LOAD_CONST               0 (None)
             20 RETURN_VALUE

>>> lc_object = list(dis.get_instructions(func_lc))[0].argval
>>> lc_object
<code object <listcomp> at 0x10d3c6780, file "<ipython-input-42-ead395105775>", line 2>
>>> dis.dis(lc_object)
  2           0 BUILD_LIST               0
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                16 (to 25)
              9 STORE_FAST               1 (x)
             12 LOAD_FAST                1 (x)
             15 LOAD_CONST               0 (2)
             18 BINARY_POWER
             19 LIST_APPEND              2
             22 JUMP_ABSOLUTE            6
        >>   25 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

另一方面,list()版本只是将生成器对象传递给list的__init__方法,然后在extend内部调用它的方法.由于对象不是列表或元组CPython,然后首先获取其迭代器,然后只是将项添加到列表中,直到迭代器耗尽:

>>> def func_ge():
    list(x**2 for x in y)
...
>>> dis.dis(func_ge)
  2           0 LOAD_GLOBAL              0 (list)
              3 LOAD_CONST               1 (<code object <genexpr> at 0x10cde6ae0, file "<ipython-input-41-f9a53483f10a>", line 2>)
              6 LOAD_CONST               2 ('func_ge.<locals>.<genexpr>')
              9 MAKE_FUNCTION            0
             12 LOAD_GLOBAL              1 (y)
             15 GET_ITER
             16 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             19 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             22 POP_TOP
             23 LOAD_CONST               0 (None)
             26 RETURN_VALUE
>>> ge_object = list(dis.get_instructions(func_ge))[1].argval
>>> ge_object
<code object <genexpr> at 0x10cde6ae0, file "<ipython-input-41-f9a53483f10a>", line 2>
>>> dis.dis(ge_object)
  2           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                15 (to 21)
              6 STORE_FAST               1 (x)
              9 LOAD_FAST                1 (x)
             12 LOAD_CONST               0 (2)
             15 BINARY_POWER
             16 YIELD_VALUE
             17 POP_TOP
             18 JUMP_ABSOLUTE            3
        >>   21 LOAD_CONST               1 (None)
             24 RETURN_VALUE
>>>
Run Code Online (Sandbox Code Playgroud)

时间比较:

>>> %timeit [x**2 for x in range(10**6)]
1 loops, best of 3: 453 ms per loop
>>> %timeit list(x**2 for x in range(10**6))
1 loops, best of 3: 478 ms per loop
>>> %%timeit
out = []
for x in range(10**6):
    out.append(x**2)
...
1 loops, best of 3: 510 ms per loop
Run Code Online (Sandbox Code Playgroud)

由于属性查找速度慢,正常循环稍慢.缓存它和时间.

>>> %%timeit
out = [];append=out.append
for x in range(10**6):
    append(x**2)
...
1 loops, best of 3: 467 ms per loop
Run Code Online (Sandbox Code Playgroud)

除了列表理解不再泄漏变量的事实之外,另外一个区别是这样的东西不再有效:

>>> [x**2 for x in 1, 2, 3] # Python 2
[1, 4, 9]
>>> [x**2 for x in 1, 2, 3] # Python 3
  File "<ipython-input-69-bea9540dd1d6>", line 1
    [x**2 for x in 1, 2, 3]
                    ^
SyntaxError: invalid syntax

>>> [x**2 for x in (1, 2, 3)] # Add parenthesis
[1, 4, 9]
>>> for x in 1, 2, 3: # Python 3: For normal loops it still works
    print(x**2)
...
1
4
9
Run Code Online (Sandbox Code Playgroud)

  • @zehnpaard引自Guido的[帖子](http://python-history.blogspot.in/2010/06/from-list-comprehensions-to-generator.html):*在你开始担心列表理解变得缓慢之前Python 3:由于Python 3中的大量实现工作一般可以加快速度,Python 3中的列表推导和生成器表达式实际上都比Python 2更快!(并且两者之间不再存在速度差异.)* (2认同)
  • @ZaarHai 这是 ipython shell,使用 `--classic` 参数运行。 (2认同)
  • @zehnpaard 差异(不再)可以忽略不计 - 第一个版本大约快 30%,另请参阅 /sf/ask/3643750561/ (2认同)

use*_*ica 12

两种形式都创建并调用匿名函数.但是,list(...)表单创建一个生成器函数并将返回的生成器迭代器传递给list,而对于[...]表单,匿名函数直接使用LIST_APPEND操作码构建列表.

以下代码获取匿名函数的反编译输出以获得示例解析及其对应的genexp-passed-to- list:

import dis

def f():
    [x for x in []]

def g():
    list(x for x in [])

dis.dis(f.__code__.co_consts[1])
dis.dis(g.__code__.co_consts[1])
Run Code Online (Sandbox Code Playgroud)

理解的输出是

  4           0 BUILD_LIST               0
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                12 (to 21)
              9 STORE_FAST               1 (x)
             12 LOAD_FAST                1 (x)
             15 LIST_APPEND              2
             18 JUMP_ABSOLUTE            6
        >>   21 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

genexp的输出是

  7           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                11 (to 17)
              6 STORE_FAST               1 (x)
              9 LOAD_FAST                1 (x)
             12 YIELD_VALUE
             13 POP_TOP
             14 JUMP_ABSOLUTE            3
        >>   17 LOAD_CONST               0 (None)
             20 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)


sch*_*ggl 5

你实际上可以证明两者可以有不同的结果来证明它们本质上是不同的:

>>> list(next(iter([])) if x > 3 else x for x in range(10))
[0, 1, 2, 3]

>>> [next(iter([])) if x > 3 else x for x in range(10)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
StopIteration
Run Code Online (Sandbox Code Playgroud)

理解中的表达式不被视为生成器,因为理解不处理StopIteration,而list构造函数则表示.

  • 请注意,在Python 3.7 / 3.8中,最上面的一个引发`RuntimeError:generator generator StopIteration`,请参见https://www.python.org/dev/peps/pep-0479/ (2认同)