¿Cuál es la sintaxis idiomática para anteponer a una lista corta de Python?

543

list.append()es la opción obvia para agregar al final de una lista. Aquí hay una explicación razonable para los desaparecidos list.prepend(). Asumiendo que mi lista es corta y las preocupaciones de rendimiento son insignificantes, es

list.insert(0, x)

o

list[0:0] = [x]

¿idiomático?

prisa
fuente

Respuestas:

784

La s.insert(0, x)forma es la más común.

Sin embargo, siempre que lo vea, puede ser el momento de considerar el uso de collections.deque en lugar de una lista.

Raymond Hettinger
fuente
99
"Sin embargo, cada vez que lo vea, puede ser el momento de considerar el uso de collections.deque en lugar de una lista". ¿Por qué es esto?
Matt M.
66
@MattM. Si inserta al principio de una lista, Python tiene que mover todos los demás elementos un espacio hacia adelante, las listas no pueden "hacer espacio en el frente". collections.deque (cola de doble extremo) tiene soporte para "hacer espacio en el frente" y es mucho más rápido en este caso.
fejfo
266

Si puede seguir el camino funcional, lo siguiente es bastante claro

new_list = [x] + your_list

Por supuesto no se ha insertado xen your_list, en lugar de haber creado una nueva lista con xpreprended a ella.

Nil Geisweiller
fuente
45
Como observa, eso no es preceder a una lista. Está creando una nueva lista. Por lo tanto, no satisface la pregunta en absoluto.
Chris Morgan
113
Si bien no satisface la pregunta, la completa, y ese es el propósito de este sitio web. Aprecio el comentario y tienes razón, pero cuando la gente busca esto, es útil verlo.
dave4jr
2
Además, si desea anteponer una lista a una lista, el uso de insertar no funcionará como se esperaba. pero este método lo hace!
gota
90

¿Cuál es la sintaxis idiomática para anteponer a una lista corta de Python?

Por lo general, no desea anteponer repetidamente a una lista en Python.

Si es corto , y no lo estás haciendo mucho ... entonces está bien.

list.insert

El list.insertpuede ser usado de esta manera.

list.insert(0, x)

Pero esto es ineficiente, porque en Python, a listes una matriz de punteros, y Python ahora debe tomar cada puntero en la lista y moverlo hacia abajo para insertar el puntero en su objeto en la primera ranura, por lo que esto es realmente eficiente para listas más bien cortas, como preguntas.

Aquí hay un fragmento de la fuente CPython donde se implementa esto, y como puede ver, comenzamos al final de la matriz y movemos todo hacia abajo por uno para cada inserción:

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

Si desea un contenedor / lista que sea eficiente para anteponer elementos, desea una lista vinculada. Python tiene una lista doblemente vinculada, que se puede insertar al principio y al final rápidamente, se llama a deque.

deque.appendleft

A collections.dequetiene muchos de los métodos de una lista. list.sortes una excepción, por lo que dequedefinitivamente no es completamente sustituible por Liskov list.

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

El dequetambién tiene un appendleftmétodo (así como popleft). El dequees una cola de doble extremo y una lista doblemente enlazada: no importa la longitud, siempre lleva la misma cantidad de tiempo pretender algo. En la notación O grande, O (1) versus el tiempo O (n) para las listas. Aquí está el 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

También es relevante el extendleftmétodo de deque , que antecede iterativamente:

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

Tenga en cuenta que cada elemento se antepondrá uno a la vez, invirtiendo efectivamente su orden.

Rendimiento de listversusdeque

Primero configuramos con algunos pretendientes 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

y rendimiento:

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

El deque es mucho más rápido. A medida que las listas se alarguen, esperaría que una deque funcione aún mejor. Si puede usar deque extendleftprobablemente obtendrá el mejor rendimiento de esa manera.

Aaron Hall
fuente
57

Si alguien encuentra esta pregunta como yo, aquí están mis pruebas de rendimiento de los métodos propuestos:

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 puede ver, la insertasignación de divisiones es casi el doble de rápida que la suma explícita y los resultados son muy parecidos. Como Raymond Hettinger señaló insertes una opción más común y yo personalmente prefiero esta forma de anteponer a la lista.

Alexey Milogradov
fuente
11
Una cosa que falta en esa prueba es la complejidad. Mientras que las dos primeras opciones tienen una complejidad constante (no se vuelve más lenta cuando hay más elementos en la lista), la tercera tiene una complejidad lineal (se hace más lenta, dependiendo de la cantidad de elementos en la lista), porque siempre tiene que copiar toda la lista. Con más elementos en la lista, el resultado puede ser mucho peor.
Dakkaron el
66
@Dakkaron Creo que te equivocas al respecto. Algunas fuentes citan la complejidad lineal para list.insert, por ejemplo, esta buena tabla , e implicada por la explicación razonable a la que se vincula el interrogador. Sospecho que CPython está reasignando cada elemento en la memoria en la lista en los primeros dos casos, por lo que los tres probablemente tengan una complejidad lineal. Sin embargo, no he mirado el código ni lo he probado, así que lamento si esas fuentes están equivocadas. Collections.deque.appendleft tiene la complejidad lineal de la que estás hablando.
TC Proctor
@Dakkaron no es cierto, todos estos tienen una complejidad equivalente. Aunque .inserty [0:0] = [0]funcionan en el lugar , todavía tienen que reasignar todo el búfer.
juanpa.arrivillaga
Estos puntos de referencia son malos. La lista inicial debe crearse en un paso de configuración separado, no como parte del tiempo en sí. Y el último crea una nueva lista de 1000001 de largo, por lo que en comparación con las otras dos versiones mutantes en el lugar se encuentran las manzanas y las naranjas.
wim