Qual é a sintaxe idiomática para anexar uma pequena lista python?


543

list.append()é a escolha óbvia para adicionar ao final de uma lista. Aqui está uma explicação razoável para os desaparecidos list.prepend(). Supondo que minha lista seja curta e as preocupações com o desempenho sejam insignificantes, é

list.insert(0, x)

ou

list[0:0] = [x]

idiomático?

Respostas:


784

O s.insert(0, x)formulário é o mais comum.

Porém, sempre que você o vir, talvez seja hora de considerar o uso de um collections.deque em vez de uma lista.


9
"Porém, sempre que você vê, pode ser hora de considerar o uso de um collections.deque em vez de uma lista." Por que é isso?
Matt M.

6
@MattM. Se você inserir na frente de uma lista, o python precisará mover todos os outros itens de um espaço para a frente; as listas não poderão "abrir espaço na frente". collections.deque (fila dupla) tem suporte para "abrir espaço na frente" e é muito mais rápido nesse caso.
fejfo 18/03

265

Se você pode seguir o caminho funcional, o seguinte é bem claro

new_list = [x] + your_list

É claro que você não inseriu , xem your_listvez disso, criou uma nova lista com a xpré-prevista.


45
Como você observa, isso não é anexado a uma lista. Está criando uma nova lista. Portanto, isso não satisfaz a pergunta.
Chris Morgan

112
Embora não satisfaça a pergunta, ela a completa, e esse é o objetivo deste site. Aprecie o comentário e você está certo, mas quando as pessoas o pesquisam, é útil ver isso.
Dave4jr

2
Além disso, se você quiser anexar uma lista a uma lista, o uso de inserção não funcionará conforme o esperado. mas esse método faz!
gota

90

Qual é a sintaxe idiomática para anexar uma pequena lista python?

Geralmente, você não deseja anexar repetidamente uma lista em Python.

Se for curto e você não estiver fazendo muito ... então tudo bem.

list.insert

O list.insertpode ser usado dessa maneira.

list.insert(0, x)

Mas isso é ineficiente, porque, em Python, a listé uma matriz de ponteiros, e agora o Python deve pegar todos os ponteiros da lista e movê-lo para baixo em um para inserir o ponteiro no seu objeto no primeiro slot, portanto, isso é realmente apenas eficiente para listas bastante curtas, como você pede.

Aqui está um trecho da fonte CPython onde isso é implementado - e como você pode ver, começamos no final da matriz e movemos tudo para baixo em um para cada inserção:

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

Se você deseja um contêiner / lista eficiente na adição de elementos, deseja uma lista vinculada. O Python tem uma lista duplamente vinculada, que pode ser inserida no início e no final rapidamente - é chamada de a deque.

deque.appendleft

A collections.dequetem muitos dos métodos de uma lista. list.sorté uma exceção, tornando dequedefinitivamente não inteiramente Liskov substituível list.

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

O dequetambém possui um appendleftmétodo (assim como popleft). O dequeé um deque e uma lista duplamente vinculada - não importa o comprimento, ele sempre leva a mesma quantidade de tempo para preprend algo. Na notação O grande, O (1) versus o tempo O (n) das listas. Aqui está o uso:

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

deque.extendleft

Também relevante é o extendleftmétodo de deque , que precede iterativamente:

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

Observe que cada elemento será anexado um de cada vez, revertendo efetivamente sua ordem.

Desempenho de listversusdeque

Primeiro, configuramos com alguns anexos iterativos:

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

e desempenho:

>>> 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

O deque é muito mais rápido. À medida que as listas ficam mais longas, eu esperaria que um deque tivesse um desempenho ainda melhor. Se você pode usar o deque, extendleftprovavelmente obterá o melhor desempenho dessa maneira.


57

Se alguém encontrar essa pergunta como eu, aqui estão meus testes de desempenho dos métodos propostos:

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

Como você pode ver, a insertatribuição de fatias é quase duas vezes mais rápida que a adição explícita e os resultados são muito próximos. Como Raymond Hettinger notou, inserté uma opção mais comum e eu, pessoalmente, prefiro essa maneira de acrescentar à lista.


11
Uma coisa que falta nesse teste é a complexidade. Enquanto as duas primeiras opções têm complexidade constante (não ficam mais lentas quando há mais elementos na lista), a terceira tem complexidade linear (fica mais lenta, dependendo da quantidade de elementos na lista), porque sempre tem que copiar a lista inteira. Com mais elementos na lista, o resultado pode ficar muito pior.
Dakkaron

6
@Dkarkaron Acho que você está errado sobre isso. Algumas fontes citam a complexidade linear para list.insert, por exemplo, esta bela tabela , e estão implícitas na explicação razoável à qual o questionador se vinculou. Eu suspeito que o CPython está realocando cada elemento na memória da lista nos dois primeiros casos, portanto todos os três provavelmente têm complexidade linear. Na verdade, eu não olhei para o código ou testei sozinho, desculpe se essas fontes estão erradas. Collections.deque.appendleft tem a complexidade linear de que você está falando.
TC Proctor

@ Dakarkaron não é verdade, todos estes têm complexidade equivalente. Embora .inserte [0:0] = [0]trabalho no local , eles ainda têm de re-alocar o buffer inteiro.
Juanpa.arrivillaga 15/02/19

Esses benchmarks são ruins. A lista inicial deve ser criada em uma etapa de configuração separada, e não parte do tempo propriamente dito. E o último cria uma nova lista com 1000001 comprimentos, então comparar com as outras duas versões mutantes no local é de maçãs e laranjas.
wim 30/01
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.