Какой идиоматический синтаксис для добавления в короткий список Python?

543

list.append()является очевидным выбором для добавления в конец списка. Вот разумное объяснение пропавшего без вести list.prepend(). Предполагая, что мой список короткий и проблемы с производительностью незначительны, это

list.insert(0, x)

или

list[0:0] = [x]

идиоматическое?

hurrymaplelad
источник

Ответы:

784

s.insert(0, x)Форма является наиболее распространенной.

Тем не менее, когда бы вы ни увидели это, возможно, пришло время рассмотреть возможность использования collection.deque вместо списка.

Раймонд Хеттингер
источник
9
«Всякий раз, когда вы видите это, возможно, пришло время рассмотреть возможность использования collection.deque вместо списка». Почему это?
Мэтт М.
6
@MattM. Если вы вставляете в начало списка, python должен переместить все остальные элементы на один пробел вперед, списки не могут «сделать пробел впереди». collection.deque (двусторонняя очередь) имеет поддержку «освобождения места спереди» и в этом случае работает намного быстрее.
Фейфо
266

Если вы можете пойти функциональным путем, следующее довольно ясно

new_list = [x] + your_list

Конечно , вы не вставили xв your_list, а вы создали новый список с xpreprended к нему.

Нил Гейсвайлер
источник
45
Как вы заметили, это не предшествует списку. Это создает новый список. Таким образом, это не удовлетворяет вопрос вообще.
Крис Морган
113
Хотя он не удовлетворяет вопрос, он округляет его, и это цель этого сайта. Оцените комментарий, и вы правы, но когда люди ищут это, полезно это увидеть.
dave4jr
2
Кроме того, если вы хотите добавить список к списку, использование вставки не будет работать должным образом. но этот метод делает!
Гота
90

Какой идиоматический синтаксис для добавления в короткий список Python?

Обычно вы не хотите повторяться перед списком в Python.

Если это коротко , и вы не делаете это много ... тогда хорошо.

list.insert

list.insertМожно использовать таким образом.

list.insert(0, x)

Но это неэффективно, потому что в Python a listявляется массивом указателей, и Python должен теперь взять каждый указатель в списке и переместить его вниз на единицу, чтобы вставить указатель на ваш объект в первом слоте, так что это действительно только эффективно для довольно коротких списков, как вы просите.

Вот фрагмент из источника CPython, где это реализовано - и, как вы можете видеть, мы начинаем с конца массива и перемещаем все вниз на единицу для каждой вставки:

for (i = n; --i >= where; )
    items[i+1] = items[i];

Если вам нужен контейнер / список, эффективный для добавления элементов, вам нужен связанный список. Python имеет двусвязный список, который можно быстро вставить в начало и в конец - он называется a deque.

deque.appendleft

А collections.dequeимеет много методов списка. list.sortисключение, делающее dequeокончательно не полностью заменяемым Лискова list.

>>> set(dir(list)) - set(dir(deque))
{'sort'}

dequeТакже имеет appendleftметод (а также popleft). Это dequeдвусторонняя очередь и двусвязный список - независимо от длины, всегда требуется одинаковое количество времени для предварительной подготовки чего-либо. В больших обозначениях O время O (1) и время O (n) для списков. Вот использование:

>>> import collections
>>> d = collections.deque('1234')
>>> d
deque(['1', '2', '3', '4'])
>>> d.appendleft('0')
>>> d
deque(['0', '1', '2', '3', '4'])

deque.extendleft

Также актуален метод deque extendleft, который итеративно добавляет:

>>> from collections import deque
>>> d2 = deque('def')
>>> d2.extendleft('cba')
>>> d2
deque(['a', 'b', 'c', 'd', 'e', 'f'])

Обратите внимание, что каждый элемент будет добавляться по одному за раз, таким образом, эффективно изменяя их порядок.

Производительность listпротивdeque

Сначала мы настроим итеративное добавление:

import timeit
from collections import deque

def list_insert_0():
    l = []
    for i in range(20):
        l.insert(0, i)

def list_slice_insert():
    l = []
    for i in range(20):
        l[:0] = [i]      # semantically same as list.insert(0, i)

def list_add():
    l = []
    for i in range(20):
        l = [i] + l      # caveat: new list each time

def deque_appendleft():
    d = deque()
    for i in range(20):
        d.appendleft(i)  # semantically same as list.insert(0, i)

def deque_extendleft():
    d = deque()
    d.extendleft(range(20)) # semantically same as deque_appendleft above

и производительность:

>>> min(timeit.repeat(list_insert_0))
2.8267281929729506
>>> min(timeit.repeat(list_slice_insert))
2.5210217320127413
>>> min(timeit.repeat(list_add))
2.0641671380144544
>>> min(timeit.repeat(deque_appendleft))
1.5863927800091915
>>> min(timeit.repeat(deque_extendleft))
0.5352169770048931

Deque намного быстрее. Поскольку списки становятся длиннее, я ожидаю, что deque будет работать еще лучше. Если вы можете использовать deque, extendleftвы, вероятно, получите лучшую производительность таким образом.

Аарон Холл
источник
57

Если кто-то найдет этот вопрос, как я, вот мои тесты производительности предлагаемых методов:

Python 2.7.8

In [1]: %timeit ([1]*1000000).insert(0, 0)
100 loops, best of 3: 4.62 ms per loop

In [2]: %timeit ([1]*1000000)[0:0] = [0]
100 loops, best of 3: 4.55 ms per loop

In [3]: %timeit [0] + [1]*1000000
100 loops, best of 3: 8.04 ms per loop

Как видите, insertи назначение среза почти в два раза быстрее, чем явное добавление, и результаты очень близки. Как отметил Рэймонд Хеттингер,insert это более распространенный вариант, и я лично предпочитаю использовать этот способ для добавления в список.

Алексей Милоградов
источник
11
Одна вещь, которая отсутствует в этом тесте, это сложность. В то время как первые два варианта имеют постоянную сложность (она не становится медленнее, когда в списке больше элементов), третья имеет линейную сложность (она становится медленнее, в зависимости от количества элементов в списке), потому что она всегда должен скопировать весь список. Чем больше элементов в списке, тем хуже результат.
Даккарон
6
@Dakkaron Я думаю, ты ошибаешься. Многие источники ссылаются на линейную сложность для list.insert, например, на эту симпатичную таблицу , и подразумевают разумное объяснение, с которым связан вопросующий . Я подозреваю, что CPython перераспределяет каждый элемент в памяти в списке в первых двух случаях, поэтому все три из них, вероятно, имеют линейную сложность. Хотя я на самом деле не смотрел код и не проверял его сам, так что извините, если эти источники неверны. Collections.deque.appendleft имеет линейную сложность, о которой вы говорите.
Проктор ТК
@Dakkaron не соответствует действительности, все они имеют эквивалентную сложность. Хотя .insertи [0:0] = [0]работают на месте , им все равно приходится перераспределять весь буфер.
juanpa.arrivillaga
Эти тесты плохие. Начальный список должен быть создан на отдельном шаге настройки, а не в самой синхронизации. И последний создает новый список длиной 1000001, поэтому по сравнению с двумя другими мутированными версиями на месте это яблоки и апельсины.
Вим