Concatenazione di due elenchi: differenza tra '+ =' ed extension ()


243

Ho visto che in realtà ci sono due (forse più) modi per concatenare elenchi in Python: un modo è usare il metodo extend ():

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

l'altro per usare l'operatore più (+):

b += a

Ora mi chiedo: quale di queste due opzioni sia il modo 'pythonic' di fare la concatenazione di elenchi e c'è una differenza tra i due (ho cercato il tutorial ufficiale di Python ma non ho trovato nulla su questo argomento).


1
Forse la differenza ha più implicazioni quando si tratta di ducktyping e se la tua forse-non-davvero-una-lista-ma-come-una-lista supporta .__iadd__()/ .__add__()/ .__radd__()contro.extend()
Nick T

Risposte:


214

L'unica differenza a livello di bytecode è che il .extendmodo in cui comporta una chiamata di funzione, che è leggermente più costoso in Python rispetto a INPLACE_ADD.

È davvero niente di cui dovresti preoccuparti, a meno che tu non stia eseguendo questa operazione miliardi di volte. È probabile, tuttavia, che il collo di bottiglia risieda in qualche altro posto.


16
Forse la differenza ha più implicazioni quando si tratta di ducktyping e se la tua forse-non-davvero-una-lista-ma-come-una-lista supporta .__iadd__()/ .__add__()/ .__radd__()contro.extend()
Nick T

8
Questa risposta non menziona le importanti differenze di ambito.
mercoledì

3
Bene, in realtà, extends è più veloce di INPLACE_ADD (), ovvero la concatenazione dell'elenco. gist.github.com/mekarpeles/3408081
Archit Kapoor,

178

Non puoi usare + = per variabile non locale (variabile che non è locale per funzione e non è globale)

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

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

    def boo():
        l += [5]

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

main()

È perché per il compilatore di case di estensione caricherà la variabile lusando l' LOAD_DEREFistruzione, ma per + = utilizzerà LOAD_FAST- e otterrai*UnboundLocalError: local variable 'l' referenced before assignment*


4
Ho delle difficoltà con la tua spiegazione "variabile che non è locale per funzione e neppure globale " potresti dare un esempio di tale variabile?
Stephane Rolland,

8
La variabile "l" nel mio esempio è esattamente di quel tipo. Non è locale per le funzioni 'foo' e 'boo' (al di fuori dei loro scopi), ma non è globale (definito all'interno della funzione 'main', non a livello di modulo)
monitorius

3
Posso confermare che questo errore si verifica ancora con Python 3.4.2 (è necessario aggiungere parentesi per stampare, ma tutto il resto può rimanere lo stesso).
trichoplax,

7
Giusto. Ma almeno puoi usare l' istruzione l non locale in boo in Python3.
monitorius,

compilatore -> interprete?
Joelb

42

Puoi concatenare le chiamate di funzione, ma non puoi + = una chiamata di funzione direttamente:

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

8

Direi che c'è una certa differenza quando si tratta di intorpidimento (ho appena visto che la domanda si pone sulla concatenazione di due elenchi, non sull'array intorpidito, ma poiché potrebbe essere un problema per i principianti, come me, spero che questo possa aiutare qualcuno che cercano la soluzione a questo post), ad es.

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

tornerà con errore

ValueError: gli operandi non possono essere trasmessi insieme alle forme (0,) (4,4,4)

b.extend(a) funziona perfettamente


5

Dal codice sorgente CPython 3.5.2 : nessuna grande differenza.

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

4

extension () funziona con qualsiasi iterabile *, + = funziona con alcuni ma può diventare 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
* abbastanza sicuro .extend () funziona con qualsiasi iterabile ma per favore commenta se non sono corretto


Tuple è sicuramente un iterabile, ma non ha il metodo extens (). Il metodo extension () non ha nulla a che fare con l'iterazione.
Wombatonfire il

.extend è un metodo della classe list. Dalla documentazione di Python: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.Suppongo di aver risposto al mio asterisco.
Grofte

Oh, intendevi dire che puoi passare qualsiasi iterabile per estendere (). L'ho letto come "extender () è disponibile per qualsiasi iterabile" :) Mio male, ma suona un po 'ambiguo.
Wombatonfire,

1
Tutto sommato, questo non è un buon esempio, almeno non nel contesto di questa domanda. Quando si utilizza un +=operatore con oggetti di tipi diversi (contrariamente a due elenchi, come nella domanda), non ci si può aspettare che si ottenga una concatenazione degli oggetti. E non puoi aspettarti che verrà listrestituito un tipo. Dai un'occhiata al tuo codice, ottieni numpy.ndarrayinvece di list.
Wombatonfire,

2

In realtà, ci sono differenze tra le tre opzioni: ADD, INPLACE_ADDe extend. Il primo è sempre più lento, mentre gli altri due sono più o meno gli stessi.

Con queste informazioni, preferirei utilizzare extend, che è più veloce di ADD, e mi sembra più esplicito di quello che stai facendo INPLACE_ADD.

Prova alcune volte il codice seguente (per 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

2
Non puoi confrontare ADDcon INPLACE_ADDe extend(). ADDproduce un nuovo elenco e copia gli elementi dei due elenchi originali su di esso. Sicuramente sarà più lento del funzionamento sul posto di INPLACE_ADDe extend().
Wombatonfire,

Lo so. Il punto di questo esempio è il confronto di diversi modi di avere un elenco con tutti gli elementi insieme. Certo ci vuole più tempo perché fa cose diverse, ma è comunque bene sapere nel caso in cui tu sia interessato a conservare inalterati gli oggetti originali.
Dalonso

1

Ho cercato il tutorial ufficiale di Python ma non sono riuscito a trovare nulla su questo argomento

Queste informazioni sembrano essere sepolte nelle FAQ sulla programmazione :

... per gli elenchi, __iadd__[vale a dire +=] equivale a richiamare extendl'elenco e restituire l'elenco. Ecco perché diciamo che per gli elenchi, +=è una "scorciatoia" perlist.extend

Puoi anche vederlo da solo nel codice sorgente di CPython: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011


-1

Secondo Python per l'analisi dei dati.

“Si noti che la concatenazione di elenchi per addizione è un'operazione relativamente costosa poiché è necessario creare un nuovo elenco e copiare gli oggetti. L'utilizzo di extendi per aggiungere elementi a un elenco esistente, specialmente se si sta creando un elenco di grandi dimensioni, è generalmente preferibile. "Quindi,

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

è più veloce dell'alternativa concatenativa:

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

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine


4
everything = everything + tempnon è necessariamente implementato nello stesso modo di everything += temp.
David Harrison,

1
Hai ragione. Grazie per il tuo promemoria. Ma il mio punto è sulla differenza di efficienza. :)
littlebear333

6
@ littlebear333 everything += tempè implementato in modo tale da everythingnon dover essere copiato. Questo rende praticamente la tua risposta un punto controverso.
nog642,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.