python - Python 3 中 `list(generator expression)` 的列表理解语法糖吗?

标签 python python-3.x list-comprehension python-internals generator-expression

在 Python 3 中,列表推导式是否只是将生成器表达式馈入 list 函数的语法糖?

例如是以下代码:

squares = [x**2 for x in range(1000)]

居然在后台转换成下面的?

squares = list(x**2 for x in range(1000))

我知道输出是相同的,Python 3 修复了列表解析所具有的周围命名空间的令人惊讶的副作用,但就 CPython 解释器在后台所做的而言,前者是转换为后者,还是代码的执行方式有什么不同吗?

背景

我在评论部分发现了与 this question 等效的声明。 ,并且快速的谷歌搜索显示了相同的声明 here .

What's New in Python 3.0 docs 中也提到了这一点。 ,但措辞有些模糊:

Also note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a list() constructor, and in particular the loop control variables are no longer leaked into the surrounding scope.

最佳答案

两者的工作方式不同。列表理解版本利用了特殊的字节码 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

另一方面,list() 版本只是将生成器对象传递给列表的__init__。然后调用其 extend 的方法内部方法。由于对象不是列表或元组,CPython 则 gets its iterator first然后简单地将项目添加到列表中,直到 iterator is exhausted :

>>> 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
>>>

时间比较:

>>> %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

由于属性查找速度慢,普通循环稍微慢一些。缓存它,然后再一次。

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

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

>>> [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

关于python - Python 3 中 `list(generator expression)` 的列表理解语法糖吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30096351/

相关文章:

mysql - 使用 load_file 和使用 mysql 和 python 动态生成的路径

python-3.x - 如何将 3d 数组转换为字符串

python - 如何在类里面实现理解

python - 将数据加载到内存中以获得更快的响应

python - 将参数传递给 timeit.Timer() 函数时棘手的 Python 字符串文字

python - Numpy 元素乘法,具有多列

Python selenium webdriver 点击功能

json - 将 github json 数据格式化为包含每日日期的 pandas 数据框

python - 什么是按属性构建字典列表的 Pythonic 方法?

python - 在 Python 中生成和压缩两个列表的最干净有效的方法