我假设你正在使用CPython。让我们比较生成的Python字节码和dis
模块。这是第一个版本:
>>> import dis
>>> def f():
... return [None] * 1000
>>> dis.dis(f)
2 0 LOAD_CONST 0 (None)
2 BUILD_LIST 1
4 LOAD_CONST 1 (1000)
6 BINARY_MULTIPLY
8 RETURN_VALUE
这很清楚:创建了一个列表
[None]
(第0-2行),然后乘以
1000
(第4-6行)。
这是第二个版本:
>>> def g():
... return [None for _ in range(1000)]
>>> dis.dis(g)
2 0 LOAD_CONST 1 (<code object <listcomp> at ..., file "<doctest __main__[3]>", line 2>)
2 LOAD_CONST 2 ('g.<locals>.<listcomp>')
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
这更加复杂:创建了一个名为g.<locals>.<listcomp>
的函数(第2行), 并且有下面代码中会看到的代码(第0行) (第4行)。创建了一个range(1000)
(第6-8-10行)和一个迭代器(第12行)。该迭代器被传递给g.<locals>.<listcomp>
函数(第14行),并返回结果(第16行)。
让我们看一下g.<locals>.<listcomp>
函数:
>>> dis.dis(g.__code__.co_consts[1])
2 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 8 (to 14)
6 STORE_FAST 1 (_)
8 LOAD_CONST 0 (None)
10 LIST_APPEND 2
12 JUMP_ABSOLUTE 4
>> 14 RETURN_VALUE
创建一个空列表(第0行),将迭代器参数(
iter(range(1000))
)推送到堆栈上(第2行),然后开始一个for循环(第4行)。循环索引的值(
_
)存储在本地数组中(第6行),并且将
None
追加到列表中(第8-10行),直到循环结束(第12行回到第4行)。
总结一下:
- 第一个版本:乘法;
- 第二个版本:创建了一个本地函数,创建了一个范围并将迭代器传递给函数;此函数逐个迭代元素并逐个添加。
第二个版本确实较慢。
注意通常的陷阱。
>>> A = [[0]] * 3
>>> A
[[0], [0], [0]]
>>> A[0].append(1)
>>> A
[[0, 1], [0, 1], [0, 1]]
但是:
>>> A = [[0] for _ in range(3)]
>>> A
[[0], [0], [0]]
>>> A[0].append(1)
>>> A
[[0, 1], [0], [0]]
如果你想知道为什么,请看上面的字节码。
l = []
是一个“空”列表。 - Chris_Rands.append
,因此每隔一段时间,整个底层数组都会被重新调整大小。如果您查看使用*
运算符进行列表重复的源代码,则会预先分配整个底层数组,从不需要调整大小,并且循环是在C级别完成的... - juanpa.arrivillaga