Łą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).

helpermethod
źródło
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.

SilentGhost
źródło
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*

monitorius
źródło
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
isarandi
źródło
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

Lance Ruo Zhang
źródło
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;
}
VicX
źródło
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ę

grofte
źródło
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
dalonsoa
źródło
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

Przejrzałem oficjalny samouczek języka Python, ale nie znalazłem nic na ten temat

Informacje te są zakopane w często zadawanych pytaniach dotyczących programowania :

... dla list, __iadd__[tj. +=] jest równoważne z wywoływaniem extendlisty i zwracaniem listy. Dlatego mówimy, że dla list +=jest „skrótem”list.extend

Możesz to również zobaczyć w kodzie źródłowym CPython: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011

Strumień
źródło
-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

littlebear333
źródło
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,