Verkettung von zwei Listen - Unterschied zwischen '+ =' und verlängern ()

243

Ich habe gesehen, dass es in Python tatsächlich zwei (vielleicht mehr) Möglichkeiten gibt, Listen zu verketten: Eine Möglichkeit ist die Verwendung der Extend () -Methode:

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

der andere, um den Plus-Operator (+) zu verwenden:

b += a

Jetzt frage ich mich: Welche dieser beiden Optionen ist die "pythonische" Methode zur Listenverkettung und gibt es einen Unterschied zwischen den beiden (ich habe das offizielle Python-Tutorial nachgeschlagen, konnte aber nichts zu diesem Thema finden).

Hilfsmethode
quelle
1
Vielleicht hat der Unterschied mehr Auswirkungen, wenn es um das Ducktyping geht und wenn Ihre vielleicht-nicht-wirklich-eine-Liste-aber-wie-eine-Liste unterstützt .__iadd__()/ .__add__()/ .__radd__()versus.extend()
Nick T

Antworten:

214

Der einzige Unterschied auf Bytecode-Ebene besteht darin, dass es .extendsich um einen Funktionsaufruf handelt, der in Python etwas teurer ist als der INPLACE_ADD.

Es ist wirklich nichts, worüber Sie sich Sorgen machen sollten, es sei denn, Sie führen diese Operation milliardenfach durch. Es ist jedoch wahrscheinlich, dass der Engpass an einem anderen Ort liegen würde.

SilentGhost
quelle
16
Vielleicht hat der Unterschied mehr Auswirkungen, wenn es um das Ducktyping geht und wenn Ihre vielleicht-nicht-wirklich-eine-Liste-aber-wie-eine-Liste unterstützt .__iadd__()/ .__add__()/ .__radd__()versus.extend()
Nick T
8
In dieser Antwort werden die wichtigen Unterschiede im Umfang nicht erwähnt.
wim
3
Tatsächlich ist Extend schneller als INPLACE_ADD (), dh die Listenverkettung. gist.github.com/mekarpeles/3408081
Archit Kapoor
178

Sie können + = nicht für nicht lokale Variablen verwenden (Variablen, die für Funktionen nicht lokal und auch nicht global sind)

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

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

    def boo():
        l += [5]

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

main()

Dies liegt daran , dass der Compiler für Extended Case die Variable lmithilfe der LOAD_DEREFAnweisung lädt , für + = jedoch LOAD_FAST- und Sie erhalten*UnboundLocalError: local variable 'l' referenced before assignment*

monitorius
quelle
4
Ich habe Schwierigkeiten mit Ihrer Erklärung "Variable, die nicht lokal für die Funktion und auch nicht global ist ". Können Sie ein Beispiel für eine solche Variable geben?
Stephane Rolland
8
Die Variable 'l' in meinem Beispiel ist genau von dieser Art. Es ist nicht lokal für 'foo' und 'boo' Funktionen (außerhalb ihres Bereichs), aber es ist nicht global (definiert innerhalb 'main' func, nicht auf Modulebene)
monitorius
3
Ich kann bestätigen, dass dieser Fehler bei Python 3.4.2 weiterhin auftritt (Sie müssen zum Drucken Klammern hinzufügen, aber alles andere kann gleich bleiben).
Trichoplax
7
Das stimmt. Aber zumindest können Sie in boo in Python3 eine nichtlokale l- Anweisung verwenden.
Monitorius
Compiler -> Interpreter?
Joelb
42

Sie können Funktionsaufrufe verketten, aber Sie können + = einen Funktionsaufruf nicht direkt:

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

Ich würde sagen, dass es einen Unterschied gibt, wenn es um Numpy geht (ich habe gerade gesehen, dass die Frage nach der Verkettung von zwei Listen und nicht nach Numpy-Array gestellt wird, aber da dies für Anfänger wie mich ein Problem sein könnte, hoffe ich, dass dies jemandem helfen kann die die Lösung für diesen Beitrag suchen), zum Beispiel.

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

es wird mit Fehler zurückgegeben

ValueError: Operanden konnten nicht zusammen mit Shapes (0,) (4,4,4) gesendet werden.

b.extend(a) funktioniert perfekt

Lance Ruo Zhang
quelle
5

Aus dem CPython 3.5.2-Quellcode : Kein großer Unterschied.

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

verlängern () funktioniert mit jedem iterierbaren *, + = funktioniert mit einigen, kann aber funky werden.

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
* ist sich ziemlich sicher, dass .extend () mit jedem iterierbaren Element funktioniert, aber bitte kommentieren Sie, wenn ich falsch bin

grofte
quelle
Tupel ist definitiv iterierbar, hat aber keine Extend () -Methode. Die Methode verlängern () hat nichts mit Iteration zu tun.
Wombatonfire
.extend ist eine Methode der Listenklasse. Aus der Python-Dokumentation: list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.Vermutlich habe ich mein eigenes Sternchen beantwortet.
Grofte
Oh, du hast gemeint, dass du jedes iterable übergeben kannst, um zu erweitern (). Ich habe es gelesen als "verlängern () ist für jedes iterable verfügbar" :) Mein schlechtes, aber es klingt ein wenig mehrdeutig.
Wombatonfire
1
Alles in allem ist dies kein gutes Beispiel, zumindest nicht im Zusammenhang mit dieser Frage. Wenn Sie einen +=Operator mit Objekten unterschiedlichen Typs verwenden (im Gegensatz zu zwei Listen, wie in der Frage), können Sie nicht erwarten, dass Sie eine Verkettung der Objekte erhalten. Und Sie können nicht erwarten, dass ein listTyp zurückgegeben wird. Schauen Sie sich Ihren Code an, Sie erhalten einen numpy.ndarraystatt list.
Wombatonfire
2

Tatsächlich gibt es Unterschiede zwischen den drei Optionen: ADD, INPLACE_ADDund extend. Ersteres ist immer langsamer, während die anderen beiden ungefähr gleich sind.

Mit diesen Informationen würde ich lieber verwenden extend, was schneller ist als ADDund mir expliziter erscheint, was Sie tun als INPLACE_ADD.

Versuchen Sie den folgenden Code einige Male (für 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
quelle
2
Sie können nicht ADDmit INPLACE_ADDund vergleichen extend(). ADDerstellt eine neue Liste und kopiert die Elemente der beiden ursprünglichen Listen in diese. Sicher ist es langsamer als der Inplace-Betrieb von INPLACE_ADDund extend().
Wombatonfire
Ich weiß das. In diesem Beispiel werden verschiedene Arten des Erstellens einer Liste mit allen Elementen zusammen verglichen. Sicher, es dauert länger, weil es verschiedene Dinge tut, aber es ist trotzdem gut zu wissen, falls Sie daran interessiert sind, die ursprünglichen Objekte unverändert zu erhalten.
Dalonsoa
1

Ich habe das offizielle Python-Tutorial nachgeschlagen, konnte aber nichts zu diesem Thema finden

Diese Informationen sind zufällig in den Programmier-FAQ vergraben :

... für Listen entspricht __iadd__[dh +=] dem Aufrufen extendder Liste und dem Zurückgeben der Liste. Deshalb sagen wir, dass für Listen +=eine "Abkürzung" für istlist.extend

Sie können dies auch im CPython-Quellcode selbst sehen: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011

Fluss
quelle
-1

Nach dem Python für Datenanalyse.

„Beachten Sie, dass die Verkettung von Listen durch Hinzufügen eine vergleichsweise teure Operation ist, da eine neue Liste erstellt und die Objekte kopiert werden müssen. Die Verwendung von "Erweitern" zum Anhängen von Elementen an eine vorhandene Liste, insbesondere wenn Sie eine große Liste erstellen, ist normalerweise vorzuziehen. So also

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

ist schneller als die verkettete Alternative:

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

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

littlebear333
quelle
4
everything = everything + tempwird nicht unbedingt auf die gleiche Weise implementiert wie everything += temp.
David Harrison
1
Du hast recht. Danke für die Erinnerung. Mein Punkt ist jedoch der Unterschied in der Effizienz. :)
littlebear333
6
@ littlebear333 everything += tempist so implementiert, dass everythinges nicht kopiert werden muss. Dies macht Ihre Antwort zu einem strittigen Punkt.
nog642