本文介绍了列表理解是Python 3中`list(generator expression)`的语法糖吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In Python 3, is a list comprehension simply syntactic sugar for a generator expression fed into the list function?

例如是以下代码:

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

实际上是在后台转换为以下内容吗?

actually converted in the background into the following?

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

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

I know the output is identical, and Python 3 fixes the surprising side-effects to surrounding namespaces that list comprehensions had, but in terms of what the CPython interpreter does under the hood, is the former converted to the latter, or are there any difference in how the code gets executed?

我在此问题的评论部分中找到了这种对等的主张,并且谷歌快速搜索显示在此处.

I found this claim of equivalence in the comments section to this question, and a quick google search showed the same claim being made here.

Python 3.0的新增功能中也提到了这一点文档,但措辞有些含糊:

推荐答案

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

Both work differently, the list comprehension version takes the advantage of special bytecode LIST_APPEND which calls PyList_Append directly for us. Hence it avoids attribute lookup to list.append and function call at Python level.

>>> 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然后获得其迭代器首先,然后只需将项目添加到列表中,直到迭代器已精疲力竭:

On the other hand the list() version simply passes the generator object to list's __init__ method which then calls its extend method internally. As the object is not a list or tuple CPython then gets its iterator first and then simply adds the items to the list until the 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

由于缓慢的属性查找,普通循环会稍微慢一些.对其进行缓存,然后再次计时.

Normal loops are slightly slow due to slow attribute lookup. Cache it and time again.

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


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


Apart from the fact that list comprehension don't leak the variables anymore one more difference is that something like this is not valid anymore:

>>> [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 3中`list(generator expression)`的语法糖吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-17 20:27