为什么[无] * 10比[无(i)范围(10)]要快10

时间:2019-10-20 20:24:58

标签: python performance list-comprehension timeit

我想创建一个带有一些初始化值的列表,因为在python中,空列表不是一个选项。因此,我开始考虑哪种方法会更快: l = [None for i in range(1000)] 要么 l = [None] * 1000 我尝试使用timeit对其进行测试:

In [56]: timeit.timeit('l = [None] * 1000', number=10000)
Out[56]: 0.04936316597741097
In [58]: timeit.timeit('l = [None for i in range(1000)]', number=10000)
Out[58]: 0.2318978540133685

[None] * 1000更快令我感到惊讶。

  1. 为什么会这样(我的性能测试方法是否正确)?
  2. 是否有更快的方法来初始化“空”列表?

1 个答案:

答案 0 :(得分:0)

我假设您正在使用CPython。让我们将生成的Python密码与dis module进行比较。这是第一个版本:

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

如果您想知道为什么,请查看上面的字节码。