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


543

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

list.insert(0, x)

или

list[0:0] = [x]

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

Ответы:


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, поэтому по сравнению с двумя другими мутированными версиями на месте это яблоки и апельсины.
Вим
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.