Concatenando dos listas: diferencia entre '+ =' y extend ()

243

He visto que en realidad hay dos (tal vez más) formas de concatenar listas en Python: una es usar el método extend ():

a = [1, 2]
b = [2, 3]
b.extend(a)

el otro para usar el operador más (+):

b += a

Ahora me pregunto: ¿Cuál de esas dos opciones es la forma 'pitónica' de hacer la concatenación de listas? ¿Hay alguna diferencia entre las dos? (He buscado el tutorial oficial de Python pero no pude encontrar nada sobre este tema)

método de ayuda
fuente
1
Tal vez la diferencia tenga más implicaciones cuando se trata de ducktyping y si su tal vez no es realmente una lista, sino una lista apoya .__iadd__()/ .__add__()/ .__radd__()versus.extend()
Nick T

Respuestas:

214

La única diferencia en un nivel de código de bytes es que la .extendforma involucra una llamada a la función, que es un poco más costosa en Python que el INPLACE_ADD.

Realmente no es nada de lo que deba preocuparse, a menos que realice esta operación miles de millones de veces. Sin embargo, es probable que el cuello de botella se encuentre en otro lugar.

SilentGhost
fuente
16
Tal vez la diferencia tenga más implicaciones cuando se trata de ducktyping y si su tal vez no es realmente una lista, sino una lista apoya .__iadd__()/ .__add__()/ .__radd__()versus.extend()
Nick T
8
Esta respuesta no menciona las diferencias de alcance importantes.
wim
3
Bueno, en realidad, extend es más rápido que INPLACE_ADD (), es decir, la concatenación de la lista. gist.github.com/mekarpeles/3408081
Archit Kapoor
178

No puede usar + = para una variable no local (variable que no es local para la función y tampoco global)

def main():
    l = [1, 2, 3]

    def foo():
        l.extend([4])

    def boo():
        l += [5]

    foo()
    print l
    boo()  # this will fail

main()

Es porque para el caso extendido , el compilador cargará la variable lusando LOAD_DEREFinstrucciones, pero para + = usará LOAD_FAST, y obtendrá*UnboundLocalError: local variable 'l' referenced before assignment*

monitorius
fuente
44
Tengo dificultades con su explicación "variable que no es local para la función y tampoco global " ¿podría dar un ejemplo de dicha variable?
Stephane Rolland
8
La variable 'l' en mi ejemplo es exactamente de ese tipo. No es local para las funciones 'foo' y 'boo' (fuera de sus ámbitos), pero no es global (definido dentro de la función 'main', no en el nivel del módulo)
monitorius
3
Puedo confirmar que este error todavía ocurre con Python 3.4.2 (necesitará agregar paréntesis para imprimir, pero todo lo demás puede permanecer igual).
trichoplax 01 de
77
Así es. Pero al menos puede usar una declaración l no local en boo en Python3.
monitorius
compilador -> intérprete?
joelb
42

Puede encadenar llamadas de función, pero no puede + = una llamada de función directamente:

class A:
    def __init__(self):
        self.listFoo = [1, 2]
        self.listBar = [3, 4]

    def get_list(self, which):
        if which == "Foo":
            return self.listFoo
        return self.listBar

a = A()
other_list = [5, 6]

a.get_list("Foo").extend(other_list)
a.get_list("Foo") += other_list  #SyntaxError: can't assign to function call
isarandi
fuente
8

Diría que hay alguna diferencia cuando se trata de numpy (acabo de ver que la pregunta es acerca de la concatenación de dos listas, no la matriz numpy, pero dado que podría ser un problema para principiantes, como yo, espero que esto pueda ayudar a alguien quienes buscan la solución a este post), por ej.

import numpy as np
a = np.zeros((4,4,4))
b = []
b += a

volverá con error

ValueError: los operandos no se pudieron transmitir junto con las formas (0,) (4,4,4)

b.extend(a) funciona perfectamente

Lance Ruo Zhang
fuente
5

Desde el código fuente de CPython 3.5.2 : No hay gran diferencia.

static PyObject *
list_inplace_concat(PyListObject *self, PyObject *other)
{
    PyObject *result;

    result = listextend(self, other);
    if (result == NULL)
        return result;
    Py_DECREF(result);
    Py_INCREF(self);
    return (PyObject *)self;
}
VicX
fuente
4

extend () funciona con cualquier iterable *, + = funciona con algunos pero puede volverse funky.

import numpy as np

l = [2, 3, 4]
t = (5, 6, 7)
l += t
l
[2, 3, 4, 5, 6, 7]

l = [2, 3, 4]
t = np.array((5, 6, 7))
l += t
l
array([ 7,  9, 11])

l = [2, 3, 4]
t = np.array((5, 6, 7))
l.extend(t)
l
[2, 3, 4, 5, 6, 7]

Python 3.6
* bastante seguro .extend () funciona con cualquier iterable pero comente si soy incorrecto

Grofte
fuente
Tuple es definitivamente un iterable, pero no tiene un método extend (). El método extend () no tiene nada que ver con la iteración.
wombatonfire
.extend es un método de la clase de lista. De la documentación de Python: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.Supongo que respondí mi propio asterisco.
grofte
Oh, quisiste decir que puedes pasar cualquier iterable para extender (). Lo leí como "extend () está disponible para cualquier iterable" :) Lo malo, pero suena un poco ambiguo.
wombatonfire
1
En general, este no es un buen ejemplo, al menos no en el contexto de esta pregunta. Cuando utiliza un +=operador con objetos de diferentes tipos (al contrario de dos listas, como en la pregunta), no puede esperar obtener una concatenación de los objetos. Y no puede esperar que haya un listtipo devuelto. Eche un vistazo a su código, obtendrá un en numpy.ndarraylugar de list.
wombatonfire
2

En realidad, existen diferencias entre las tres opciones: ADD, INPLACE_ADDy extend. El primero siempre es más lento, mientras que los otros dos son más o menos lo mismo.

Con esta información, preferiría usar extend, que es más rápido ADDy me parece más explícito de lo que está haciendo que INPLACE_ADD.

Pruebe el siguiente código varias veces (para Python 3):

import time

def test():
    x = list(range(10000000))
    y = list(range(10000000))
    z = list(range(10000000))

    # INPLACE_ADD
    t0 = time.process_time()
    z += x
    t_inplace_add = time.process_time() - t0

    # ADD
    t0 = time.process_time()
    w = x + y
    t_add = time.process_time() - t0

    # Extend
    t0 = time.process_time()
    x.extend(y)
    t_extend = time.process_time() - t0

    print('ADD {} s'.format(t_add))
    print('INPLACE_ADD {} s'.format(t_inplace_add))
    print('extend {} s'.format(t_extend))
    print()

for i in range(10):
    test()
ADD 0.3540440000000018 s
INPLACE_ADD 0.10896000000000328 s
extend 0.08370399999999734 s

ADD 0.2024550000000005 s
INPLACE_ADD 0.0972940000000051 s
extend 0.09610200000000191 s

ADD 0.1680199999999985 s
INPLACE_ADD 0.08162199999999586 s
extend 0.0815160000000077 s

ADD 0.16708400000000267 s
INPLACE_ADD 0.0797719999999913 s
extend 0.0801490000000058 s

ADD 0.1681250000000034 s
INPLACE_ADD 0.08324399999999343 s
extend 0.08062700000000689 s

ADD 0.1707760000000036 s
INPLACE_ADD 0.08071900000000198 s
extend 0.09226200000000517 s

ADD 0.1668420000000026 s
INPLACE_ADD 0.08047300000001201 s
extend 0.0848089999999928 s

ADD 0.16659500000000094 s
INPLACE_ADD 0.08019399999999166 s
extend 0.07981599999999389 s

ADD 0.1710910000000041 s
INPLACE_ADD 0.0783479999999912 s
extend 0.07987599999999873 s

ADD 0.16435900000000458 s
INPLACE_ADD 0.08131200000001115 s
extend 0.0818660000000051 s
dalonsoa
fuente
2
No se puede comparar ADDcon INPLACE_ADDy extend(). ADDproduce una nueva lista y copia los elementos de las dos listas originales. Seguro que será más lento que la operación in situ de INPLACE_ADDy extend().
wombatonfire
Yo sé eso. El objetivo de este ejemplo es comparar diferentes formas de tener una lista con todos los elementos juntos. Claro que lleva más tiempo porque hace cosas diferentes, pero aún así es bueno saber en caso de que esté interesado en preservar los objetos originales sin alteraciones.
dalonsoa
1

Busqué el tutorial oficial de Python pero no pude encontrar nada sobre este tema.

Esta información está oculta en las Preguntas frecuentes sobre programación :

... para listas, __iadd__[es decir +=] es equivalente a llamar extenda la lista y devolver la lista. Es por eso que decimos que para las listas, +=es una "taquigrafía" paralist.extend

También puede ver esto por sí mismo en el código fuente de CPython: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011

Flujo
fuente
-1

De acuerdo con Python para el análisis de datos.

“Tenga en cuenta que la concatenación de listas por adición es una operación relativamente costosa ya que se debe crear una nueva lista y copiar los objetos. Por lo general, es preferible utilizar extender para agregar elementos a una lista existente, especialmente si está creando una lista grande. "Por lo tanto,

everything = []
for chunk in list_of_lists:
    everything.extend(chunk)

es más rápido que la alternativa concatenativa:

everything = []
for chunk in list_of_lists:
    everything = everything + chunk

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

littlebear333
fuente
44
everything = everything + tempno se implementa necesariamente de la misma manera que everything += temp.
David Harrison el
1
Tienes razón. Gracias por recordarmelo. Pero mi punto es sobre la diferencia de eficiencia. :)
littlebear333
66
@ littlebear333 everything += tempse implementa de tal manera que everythingno es necesario copiarlo. Esto hace que su respuesta sea un punto discutible.
nog642