なぜリストを理解するのがリストに追加するよりもずっと速いのかと思っていました。違いは表現力にすぎないと思いましたが、違いはありません。
>>> 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%高速です。どうして?
リスト内包表記は、基本的に通常の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では、リスト内包表記を使用する2番目の関数にはappend
属性がありません。これらの余分なバイトコードはすべて、追加のアプローチを遅くします。また、各反復でappend
属性のロードも行うことに注意してください。これにより、リスト内包表記を使用する2番目の関数よりもコードの時間が約2倍遅くなります。
append
関数の検索とロードにかかる時間を考慮しても、リストはPythonで一度に1つのアイテムを作成するのではなくCで作成されるため、リストの理解はさらに高速です。
# 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)
this の記事を引用しているのは、append
のlist
属性が検索されず、ロードされ、関数として呼び出されないためです。繰り返し以上。