我想知道为什么列表理解比附加到列表要快得多。我认为差异只是富有表现力,但事实并非如此。
>>> import timeit
>>> timeit.timeit(stmt='''\
t = []
for i in range(10000):
t.append(i)''', number=10000)
9.467898777974142
>>> timeit.timeit(stmt='t= [i for i in range(10000)]', number=10000)
4.1138417314859
列表理解速度提高了50%。为什么呢?
答案 0 :(得分:58)
列表理解基本上只是一个"语法糖"对于常规for
循环。在这种情况下,它表现更好的原因是因为它不需要加载列表的append属性并在每次迭代时将其作为函数调用。换句话说,一般,列表推导执行得更快,因为暂停和恢复功能的帧,或者在其他情况下的多个功能,比按需创建列表要慢。
请考虑以下示例:
# Python-3.6
In [1]: import dis
In [2]: def f1():
...: l = []
...: for i in range(5):
...: l.append(i)
...:
In [3]: def f2():
...: [i for i in range(5)]
...:
In [4]: dis.dis(f1)
2 0 BUILD_LIST 0
3 STORE_FAST 0 (l)
3 6 SETUP_LOOP 33 (to 42)
9 LOAD_GLOBAL 0 (range)
12 LOAD_CONST 1 (5)
15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
18 GET_ITER
>> 19 FOR_ITER 19 (to 41)
22 STORE_FAST 1 (i)
4 25 LOAD_FAST 0 (l)
28 LOAD_ATTR 1 (append)
31 LOAD_FAST 1 (i)
34 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
37 POP_TOP
38 JUMP_ABSOLUTE 19
>> 41 POP_BLOCK
>> 42 LOAD_CONST 0 (None)
45 RETURN_VALUE
In [5]: dis.dis(f2)
2 0 LOAD_CONST 1 (<code object <listcomp> at 0x7fe48b2265d0, file "<ipython-input-3-9bc091d521d5>", line 2>)
3 LOAD_CONST 2 ('f2.<locals>.<listcomp>')
6 MAKE_FUNCTION 0
9 LOAD_GLOBAL 0 (range)
12 LOAD_CONST 3 (5)
15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
18 GET_ITER
19 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
22 POP_TOP
23 LOAD_CONST 0 (None)
26 RETURN_VALUE
你可以在偏移22看到我们在第一个函数中有一个append
属性,因为我们在第二个函数中没有使用列表理解这样的东西。所有这些额外的字节码将使附加方法变慢。另请注意,在每次迭代中,您还会加载append
属性,这使得您的代码比使用列表推导的第二个函数慢大约2倍。
答案 1 :(得分:9)
即使分解查找和加载append
函数所花费的时间,列表理解仍然更快,因为列表是用C创建的,而不是在Python中一次构建一个项目。
# Slow
timeit.timeit(stmt='''
for i in range(10000):
t.append(i)''', setup='t=[]', number=10000)
# Faster
timeit.timeit(stmt='''
for i in range(10000):
l(i)''', setup='t=[]; l=t.append', number=10000)
# Faster still
timeit.timeit(stmt='t = [i for i in range(10000)]', number=10000)
答案 2 :(得分:5)
引用this文章,这是因为append
的{{1}}属性未被查找,加载并作为一个函数调用,这需要时间和加起来过迭代。