在Python 3,是一个列表理解为送入生成器表达式只是语法糖list
功能?
例如是如下代码:
squares = [x**2 for x in range(1000)]
在后台成以下实际转换?
squares = list(x**2 for x in range(1000))
我知道这个输出是相同的,和Python 3修复了令人惊讶的副作用周围,列出推导了命名空间,但在什么样的CPython的解释引擎盖下做来讲,是前者转换为后者,还是有什么区别在代码如何被执行?
背景
我发现,在评论部分等价的这一主张这个问题 ,和快速谷歌搜索显示了同样的要求正在作出这里 。
也有在这方面的一些提什么新的Python 3.0文档 ,但措辞有些含糊:
还要注意,列表内涵有不同的语义:它们更接近语法糖生成器表达式列表()构造函数中,特别是循环控制变量不再泄漏到周围的范围。
这两种工作方式不同,该列表理解的版本采用了特殊的字节码的优势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则获得其第一个迭代器 ,然后简单地添加项目到列表中,直到迭代耗尽 :
>>> 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
这两种形式的创建和调用一个匿名函数。 然而, list(...)
形式产生发电机功能,并传递返回生成器-迭代到list
,同时与[...]
的形式中,匿名函数直接建立列表LIST_APPEND
操作码。
以下代码获取的匿名函数反编译输出的示例理解及其相应genexp-传入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])
为理解的输出
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
对于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
实际上,你可以表明,这两种可以有不同的结果,以证明他们是本质上的不同:
>>> 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
修真内的表达不被视为因为理解不处理发电机StopIteration
而, list
构造函数。
他们是不一样的, list()
将评估过之后是什么在括号内执行完毕,而不是之前给它什么。
在[]
在python是有点神奇,它告诉蟒蛇来包装是里面什么都为列表,更像是一种暗示的语言。
文章来源: Are list comprehensions syntactic sugar for `list(generator expression)` in Python 3?