Итерация против Конкатенации списков

Итак, есть два способа взять список и добавить участников второго списка в первый. Вы можете использовать конкатенацию списка или свою итерацию по ней. Вы можете:

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.