Łączenie dwóch list - różnica między „+ =” a ext ()


243

Widziałem, że istnieją w rzeczywistości dwa (może więcej) sposoby łączenia list w Pythonie: Jednym ze sposobów jest użycie metody ext ():

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

drugi, aby użyć operatora plus (+):

b += a

Teraz zastanawiam się: która z tych dwóch opcji jest „pytonicznym” sposobem na łączenie list i czy istnieje między nimi różnica (przejrzałem oficjalny samouczek języka Python, ale nic nie mogłem znaleźć na ten temat).


1
Być może różnica ma więcej implikacji, jeśli chodzi o tworzenie uników i jeśli twoja lista może nie jest naprawdę, ale podobna do listy obsługuje .__iadd__()/ .__add__()/ w .__radd__()porównaniu.extend()
Nick T

Odpowiedzi:


214

Jedyną różnicą na poziomie kodu bajtowego jest to, że .extendsposób obejmuje wywołanie funkcji, które jest nieco droższe w Pythonie niż INPLACE_ADD.

To naprawdę nie powinieneś się martwić, chyba że wykonasz tę operację miliardy razy. Jest jednak prawdopodobne, że wąskie gardło znalazłoby się w innym miejscu.


16
Być może różnica ma więcej implikacji, jeśli chodzi o tworzenie uników i jeśli twoja lista może nie jest naprawdę, ale podobna do listy obsługuje .__iadd__()/ .__add__()/ w .__radd__()porównaniu.extend()
Nick T

8
Ta odpowiedź nie wspomina o istotnych różnicach dotyczących zakresu.
wim

3
No cóż, rozszerzenie jest szybsze niż INPLACE_ADD (), tj. Konkatenacja listy. gist.github.com/mekarpeles/3408081
Archit Kapoor

178

Nie można użyć + = dla zmiennej nielokalnej (zmiennej, która nie jest lokalna dla funkcji, a także nie globalna)

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

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

    def boo():
        l += [5]

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

main()

Wynika to z tego, że dla rozszerzonego przypadku kompilator załaduje zmienną lza pomocą LOAD_DEREFinstrukcji, ale dla + = użyje LOAD_FAST- i otrzymasz*UnboundLocalError: local variable 'l' referenced before assignment*


4
Mam problem z wyjaśnieniem „zmienna, która nie jest lokalna dla funkcji, a także nie globalna ”. Czy mógłbyś podać przykład takiej zmiennej?
Stephane Rolland

8
Zmienna „l” w moim przykładzie jest dokładnie tego rodzaju. Nie jest lokalny dla funkcji „foo” i „boo” (poza ich zakresami), ale nie jest globalny (zdefiniowany w funkcji „main”, nie na poziomie modułu)
monitorius

3
Mogę potwierdzić, że ten błąd nadal występuje w Pythonie 3.4.2 (musisz dodać nawiasy do wydrukowania, ale wszystko inne może pozostać niezmienione).
trichoplax

7
Zgadza się. Ale przynajmniej możesz użyć nielokalnej instrukcji l w boo w Python3.
monitorius

kompilator -> tłumacz?
joelb

42

Możesz łączyć wywołania funkcji, ale nie możesz + = wywołania funkcji bezpośrednio:

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

Powiedziałbym, że istnieje pewna różnica, jeśli chodzi o numpy (właśnie zobaczyłem, że pytanie dotyczy połączenia dwóch list, a nie tablicy numpy, ale ponieważ może to być problem dla początkujących, takich jak ja, mam nadzieję, że to może komuś pomóc którzy szukają rozwiązania tego postu), np.

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

wróci z błędem

ValueError: operandy nie można nadawać razem z kształtami (0,) (4,4,4)

b.extend(a) działa świetnie


5

Z kodu źródłowego CPython 3.5.2 : Brak dużej różnicy.

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

ext () działa z każdym iterowalnym *, + = działa z niektórymi, ale może stać się 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
* jest całkiem pewny, że .extend () działa z każdym iterowalnym, ale proszę o komentarz, jeśli się mylę


Tuple jest zdecydowanie iterowalny, ale nie ma metody ext (). Metoda ext () nie ma nic wspólnego z iteracją.
wombatonfire

.extend jest metodą klasy list. Z dokumentacji Pythona: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.Chyba odpowiedziałem na własną gwiazdkę.
grofte

Och, miałeś na myśli, że możesz przekazać dowolną iterowalną metodę ext (). Przeczytałem to jako „przedłużyć () jest dostępne dla każdego iterowalnego” :) Moje złe, ale brzmi to trochę niejednoznacznie.
wombatonfire

1
Podsumowując, nie jest to dobry przykład, przynajmniej nie w kontekście tego pytania. Kiedy używasz +=operatora z obiektami różnego typu (w przeciwieństwie do dwóch list, jak w pytaniu), nie możesz oczekiwać, że uzyskasz konkatenację obiektów. I nie możesz oczekiwać, że zostanie listzwrócony typ. Spójrz na swój kod, otrzymasz numpy.ndarrayzamiast list.
wombatonfire

2

Faktycznie, istnieją różnice pomiędzy tymi trzema opcjami: ADD, INPLACE_ADDi extend. Pierwsza jest zawsze wolniejsza, a pozostałe dwie są mniej więcej takie same.

Korzystając z tych informacji, wolałbym użyć extend, który jest szybszy niż ADDi wydaje mi się bardziej wyraźny niż to, co robisz INPLACE_ADD.

Wypróbuj następujący kod kilka razy (dla 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
Nie możesz porównać ADDz INPLACE_ADDi extend(). ADDtworzy nową listę i kopiuje do niej elementy dwóch oryginalnych list. Na pewno będzie on wolniejszy niż działanie w miejscu INPLACE_ADDi extend().
wombatonfire

Wiem to. Celem tego przykładu jest porównanie różnych sposobów tworzenia listy ze wszystkimi elementami razem. Pewnie, że zajmuje to więcej czasu, ponieważ robi różne rzeczy, ale nadal dobrze jest wiedzieć, na wypadek gdybyś chciał zachować oryginalne obiekty bez zmian.
dalonsoa


-1

Według Pythona do analizy danych.

„Należy zauważyć, że konkatenacja listy przez dodanie jest stosunkowo kosztowną operacją, ponieważ należy utworzyć nową listę i skopiować obiekty. Zazwyczaj preferowane jest użycie rozszerzania w celu dołączenia elementów do istniejącej listy, szczególnie w przypadku tworzenia dużej listy. „Tak więc

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

jest szybszy niż konkatenatywna alternatywa:

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

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj


4
everything = everything + tempniekoniecznie jest implementowany w taki sam sposób jak everything += temp.
David Harrison,

1
Masz rację. Dziękuję za przypomnienie. Ale chodzi mi o różnicę wydajności. :)
littlebear333

6
@ littlebear333 everything += tempjest zaimplementowany w taki sposób, że everythingnie trzeba go kopiować. To właściwie czyni twoją odpowiedź sporną kwestią.
nog642,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.