Ответ 1
Понимание списка - это просто "синтаксический сахар" для обычного цикла for
. В этом случае причина того, что он работает лучше, заключается в том, что ему не нужно загружать атрибут добавления в список и вызывать его как функцию на каждой итерации. Другими словами, и в целом, списки выполняются быстрее, потому что приостановка и возобновление функционального фрейма или нескольких функций в других случаях медленнее, чем создание списка по требованию.
Рассмотрим следующие примеры:
# 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 раза медленнее, чем вторая функция, использующая понимание списка.