Який ідіоматичний синтаксис для подання до короткого списку пітонів?


543

list.append()це очевидний вибір для додавання до кінця списку. Ось розумне пояснення зниклих list.prepend(). Якщо припустити, що мій список короткий, а занепокоєння щодо діяльності незначні, є

list.insert(0, x)

або

list[0:0] = [x]

ідіоматичний?

Відповіді:


783

s.insert(0, x)Форма є найбільш поширеною.

Щоразу, коли ви бачите це, можливо, час подумати про використання колекції collection.deque замість списку.


9
"Щоразу, коли ви бачите це, можливо, час розглянути можливість використання колекції.deque замість списку." Чому це?
Метт М.

6
@MattM. Якщо ви вставляєте внизу списку, python повинен перемістити всі інші елементи на один простір вперед, списки не можуть "зробити простір спереду". collection.deque (дворядна черга) має підтримку для "створення місця спереду" і набагато швидше в цьому випадку.
fejfo

265

Якщо ви можете піти функціональним шляхом, наступне досить зрозуміло

new_list = [x] + your_list

Звичайно , ви не вставили xв your_list, а ви створили новий список з xpreprended до нього.


45
Як ви зауважуєте, це не передує списку. Це створює новий список. Таким чином, це зовсім не задовольняє питання.
Кріс Морган

112
Хоча це питання не задовольняє, воно закручує його, і це є метою цього веб-сайту. Оцініть коментар і ви маєте рацію, але коли люди це шукають, корисно це побачити.
dave4jr

2
Крім того, якщо ви хочете додати список до списку, то використання вставки не буде працювати, як очікувалося. але цей спосіб робить!
gota

89

Який ідіоматичний синтаксис для подання до короткого списку пітонів?

Зазвичай ви не хочете, щоб повторно додавати до списку в 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

A 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

Також актуальним є 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's, 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 має лінійну складність, про яку ви говорите.
TC Proctor

@Dakkaron не відповідає дійсності, всі вони мають однакову складність. Хоча .insertі [0:0] = [0]працюють на місці , їм все одно доведеться перерозподілити весь буфер.
juanpa.arrivillaga

Ці орієнтири погані. Початковий список повинен бути створений окремим кроком налаштування, а не частиною самих термінів. І останній створює новий список довжиною 1000001, тому порівняння з двома іншими мутуючими місцевими версіями - це яблука та апельсини.
Вім
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.