Concatenando duas listas - diferença entre '+ =' e extend ()

243

Eu vi que existem duas maneiras (talvez mais) de concatenar listas no Python: Uma maneira é usar o método extend ():

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

o outro para usar o operador mais (+):

b += a

Agora, eu me pergunto: Qual dessas duas opções é a maneira 'pitônica' de concatenação de listas e existe uma diferença entre as duas (procurei o tutorial oficial do Python, mas não consegui encontrar nada sobre esse tópico).

helpermethod
fonte
1
Talvez a diferença tem mais implicações quando se trata de Duck Typing e se o talvez-não-muito-a-lista, mas como-um-lista de suportes .__iadd__()/ .__add__()/ .__radd__()contra.extend()
Nick T

Respostas:

214

A única diferença no nível de bytecode é que o .extendcaminho envolve uma chamada de função, que é um pouco mais cara no Python do que no INPLACE_ADD.

Você realmente não deve se preocupar com nada, a menos que esteja executando esta operação bilhões de vezes. É provável, no entanto, que o gargalo esteja em outro lugar.

SilentGhost
fonte
16
Talvez a diferença tem mais implicações quando se trata de Duck Typing e se o talvez-não-muito-a-lista, mas como-um-lista de suportes .__iadd__()/ .__add__()/ .__radd__()contra.extend()
Nick T
8
Essa resposta não menciona as importantes diferenças de escopo.
wim 27/01
3
Bem, na verdade, estende é mais rápido que o INPLACE_ADD (), ou seja, a concatenação da lista. gist.github.com/mekarpeles/3408081
Kapoor
178

Você não pode usar + = para variável não local (variável que não é local para a função e também não é global)

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

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

    def boo():
        l += [5]

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

main()

É porque, para o caso de extensão , o compilador carregará a variável lusando a LOAD_DEREFinstrução, mas para + = ele usará LOAD_FAST- e você obtém*UnboundLocalError: local variable 'l' referenced before assignment*

monitorius
fonte
4
Estou tendo dificuldades com a sua explicação "variável que não é local para a função e também não é global ", você poderia dar um exemplo dessa variável?
precisa saber é o seguinte
8
A variável 'l' no meu exemplo é exatamente desse tipo. Não é local para 'foo' e funções 'boo' (fora de seus escopos), mas não é global (dentro definido 'main' func, não no nível de módulo)
monitorius
3
Posso confirmar que esse erro ainda ocorre no python 3.4.2 (você precisará adicionar parênteses para imprimir, mas todo o resto pode permanecer o mesmo).
Trichoplax
7
Está certo. Mas pelo menos você pode usar a instrução l não-local no boo no Python3.
Monitorius
compilador -> intérprete?
joelb 17/03
42

Você pode encadear chamadas de função, mas não pode + = uma chamada de função diretamente:

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

Eu diria que há alguma diferença quando se trata de numpy (acabei de ver que a pergunta é sobre concatenar duas listas, não numpy array, mas como pode ser um problema para iniciantes, como eu, espero que isso ajude alguém que buscam a solução para este post), por ex.

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

retornará com erro

ValueError: os operandos não puderam ser transmitidos junto com as formas (0,) (4,4,4)

b.extend(a) funciona perfeitamente

Lance Ruo Zhang
fonte
5

Do código fonte do CPython 3.5.2 : Não há grande diferença.

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

extend () funciona com qualquer iterável *, + = funciona com alguns, mas pode ficar estranho.

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
* com certeza .extend () funciona com qualquer iterável, mas comente se estiver incorreto

grofte
fonte
A tupla é definitivamente um iterável, mas não possui o método extend (). O método extend () não tem nada a ver com iteração.
Wombatonfire 22/03/19
.extend é um método da classe list. Da documentação do Python: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.Acho que respondi meu próprio asterisco.
grofte 23/03/19
Ah, você quis dizer que pode passar qualquer iterável para estender (). Eu li como "extend () está disponível para qualquer iterável" :) Meu mal, mas parece um pouco ambíguo.
Wombatonfire 23/03/19
1
Em suma, este não é um bom exemplo, pelo menos não no contexto desta questão. Quando você usa um +=operador com objetos de tipos diferentes (ao contrário de duas listas, como na pergunta), não pode esperar que obtenha uma concatenação dos objetos. E você não pode esperar que haja um listtipo retornado. Dê uma olhada no seu código, você recebe um em numpy.ndarrayvez de list.
wombatonfire
2

Na verdade, existem diferenças entre as três opções: ADD, INPLACE_ADDe extend. O primeiro é sempre mais lento, enquanto os outros dois são aproximadamente os mesmos.

Com essas informações, prefiro usar o extendque é mais rápido do que ADDe me parece mais explícito do que você está fazendo INPLACE_ADD.

Tente o seguinte código algumas vezes (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
fonte
2
Você não pode comparar ADDcom INPLACE_ADDe extend(). ADDproduz uma nova lista e copia os elementos das duas listas originais. Com certeza será mais lento que a operação local de INPLACE_ADDe extend().
Wombatonfire 22/03/19
Eu sei disso. O objetivo deste exemplo é comparar maneiras diferentes de ter uma lista com todos os elementos juntos. Claro que leva mais tempo porque faz coisas diferentes, mas ainda assim é bom saber se você está interessado em preservar os objetos originais inalterados.
dalonsoa
1

Eu procurei o tutorial oficial do Python, mas não consegui encontrar nada sobre este tópico

Esta informação está oculta nas Perguntas frequentes sobre programação :

... para listas, __iadd__[ie +=] é equivalente a entrar extendna lista e retornar a lista. É por isso que dizemos que, para listas, +=é uma "abreviação" paralist.extend

Você também pode ver isso no código fonte do CPython: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011

Fluxo
fonte
-1

De acordo com o Python para análise de dados.

“Observe que a concatenação da lista por adição é uma operação relativamente cara, pois uma nova lista deve ser criada e os objetos copiados. Geralmente, é preferível usar o estender para anexar elementos a uma lista existente, especialmente se você estiver criando uma lista grande. " Portanto,

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

é mais rápido que a alternativa concatenativa:

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

insira a descrição da imagem aqui insira a descrição da imagem aqui

littlebear333
fonte
4
everything = everything + tempnão é necessariamente implementado da mesma maneira que everything += temp.
David Harrison
1
Você está certo. Obrigado por seu lembrete. Mas meu argumento é sobre a diferença de eficiência. :)
littlebear333
6
@ littlebear333 everything += tempé implementado de uma maneira que everythingnão precisa ser copiada. Isso praticamente faz da sua resposta um ponto discutível.
nog642