Итерация против Конкатенации списков
Итак, есть два способа взять список и добавить участников второго списка в первый. Вы можете использовать конкатенацию списка или свою итерацию по ней. Вы можете:
for obj in list2:
list1.append(obj)
или вы можете:
list1 = list1 + list2
или
list1 += list2
Мой вопрос: что быстрее, и почему? Я протестировал это, используя два чрезвычайно больших списка (более 10000 объектов), и казалось, что метод повторения был намного быстрее, чем объединение конкатенации (как в l1 = l1 + l2). Почему это? Может кто-нибудь объяснить?
Ответы
Ответ 1
append
добавляет каждый элемент по одному, что является причиной его медленности, а также повторные вызовы функций append
.
Однако в этом случае оператор +=
представляет синтаксический сахар не для +
. Оператор +=
фактически не создает новый список и не назначает его обратно, он изменяет левый операнд на месте. Это довольно очевидно при использовании timeit
для использования как 10 000 раз.
>>> timeit.timeit(stmt="l = l + j", setup="l=[1,2,3,4]; j = [5,6,7,8]", number=10000)
0.5794978141784668
>>> timeit.timeit(stmt="l += j", setup="l=[1,2,3,4]; j = [5,6,7,8]", number=10000)
0.0013298988342285156
+=
намного быстрее (около 500x)
У вас также есть метод extend
для списков, которые могут добавлять любой итеративный (а не только другой список) с чем-то вроде l.extend(l2)
>>> timeit.timeit(stmt="l.extend(j)", setup="l=[1,2,3,4]; j = [5,6,7,8]", number=10000)
0.0016009807586669922
>>> timeit.timeit(stmt="for e in j: l.append(e)", setup="l=[1,2,3,4]; j = [5,6,7,8]", number=10000)
0.00805807113647461
Логически эквивалентно добавлению, но гораздо быстрее, чем вы можете видеть.
Итак, чтобы объяснить это:
итерация выполняется быстрее, чем +
, потому что +
должен построить весь новый список
extend
быстрее, чем итерация, потому что это встроенный метод списка и оптимизирован. Логически эквивалентно добавлению несколько раз, но осуществляется по-разному.
+=
быстрее, чем extend
, потому что он может изменить список на месте, зная, насколько большим должен быть список и без повторных вызовов функций. Предполагается, что вы добавляете свой список в другой список/tuple
Ответ 2
Я запустил следующий код
l1 = list(range(0, 100000))
l2 = list(range(0, 100000))
def t1():
starttime = time.monotonic()
for item in l1:
l2.append(item)
print(time.monotonic() - starttime)
l1 = list(range(0, 100000))
l2 = list(range(0, 100000))
def t2():
starttime = time.monotonic()
global l1
l1 += l2
print(time.monotonic() - starttime)
и получил это, в котором говорится, что добавление списков (+ =) выполняется быстрее.
+0,016047026962041855
+0,0019438499584794044
Ответ 3
Вы неправильно измеряете; повторение и вызов append
несколько раз медленнее, чем выполнение одного вызова, поскольку накладные расходы на вызов многих функций (по крайней мере, на cpython) затмевают все, что связано с фактической работой списка, как показано здесь с помощью cPython 2.7.5 на Linux x64:
$ python -m timeit -s 'x = range(10000);y = range(10000)' 'for e in y:x.append(e)'
100 loops, best of 3: 2.56 msec per loop
$ python -m timeit -s 'x = range(10000);y = range(10000)' 'x = x + y'
100 loops, best of 3: 8.98 msec per loop
$ python -m timeit -s 'x = range(10000);y = range(10000)' 'x += y'
10000 loops, best of 3: 105 usec per loop
$ python -m timeit -s 'x = range(10000);y = range(10000)' 'x.extend(y)'
10000 loops, best of 3: 107 usec per loop
Обратите внимание, что x = x + y
создает вторую копию списка (по крайней мере, в cPython). x.extend(y)
и его двоюродный брат x += y
делают то же самое, что и вызов append
несколько раз, просто без накладных расходов, фактически вызывающих метод Python.