Warum funktioniert b + = (4,) und b = b + (4,) funktioniert nicht, wenn b eine Liste ist?

77

Wenn wir nehmen b = [1,2,3]und wenn wir versuchen:b+=(4,)

Es kehrt zurück b = [1,2,3,4], aber wenn wir es versuchen, b = b + (4,)funktioniert es nicht.

b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists

Ich habe erwartet b+=(4,), dass dies fehlschlägt, da Sie keine Liste und kein Tupel hinzufügen können, aber es hat funktioniert. Also habe ich versucht b = b + (4,), das gleiche Ergebnis zu erwarten, aber es hat nicht funktioniert.

Supun Dasantha Kuruppu
quelle
4
Ich glaube, eine Antwort finden Sie hier .
Jochen
Zuerst habe ich das falsch verstanden und versucht, es als zu breit zu schließen, dann habe ich es zurückgezogen. Dann dachte ich, es müsste ein Duplikat sein, aber ich konnte nicht nur keine Stimme abgeben, sondern zog mir auch die Haare aus, um andere Antworten wie diese zu finden. : /
Karl Knechtel
Sehr ähnliche Frage: stackoverflow.com/questions/58048664/…
Sanyash

Antworten:

70

Das Problem mit "Warum" -Fragen ist, dass sie normalerweise mehrere verschiedene Dinge bedeuten können. Ich werde versuchen, jeden zu beantworten, von dem ich glaube, dass Sie ihn im Sinn haben.

"Warum kann es anders funktionieren?" was zB von diesem beantwortet wird . Grundsätzlich wird +=versucht, verschiedene Methoden des Objekts zu verwenden: __iadd__(die nur auf der linken Seite aktiviert sind), vs __add__und __radd__("Reverse Add", auf der rechten Seite aktiviert, wenn die linke Seite keine hat __add__) für +.

"Was genau macht jede Version?" Kurz gesagt, die list.__iadd__Methode macht dasselbe wie list.extend(aber aufgrund des Sprachdesigns gibt es immer noch eine Zuordnung zurück).

Dies bedeutet zum Beispiel auch, dass

>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]

+Erstellt natürlich ein neues Objekt, erfordert jedoch explizit eine andere Liste, anstatt zu versuchen, Elemente aus einer anderen Sequenz herauszuziehen.

"Warum ist es für + = nützlich, dies zu tun? Es ist effizienter; die extendMethode muss kein neues Objekt erstellen. Natürlich hat dies manchmal einige überraschende Auswirkungen (wie oben), und im Allgemeinen geht es bei Python nicht wirklich um Effizienz , aber diese Entscheidungen wurden vor langer Zeit getroffen.

"Was ist der Grund, das Hinzufügen von Listen und Tupeln mit + nicht zuzulassen?" Siehe hier (danke, @ splash58); Eine Idee ist, dass (Tupel + Liste) den gleichen Typ wie (Liste + Tupel) erzeugen sollte, und es ist nicht klar, welcher Typ das Ergebnis sein sollte. +=hat dieses problem nicht, da a += bsollte natürlich den typ von nicht ändern a.

Karl Knechtel
quelle
2
Oof, ganz richtig. Und Listen werden nicht verwendet |, so dass mein Beispiel irgendwie ruiniert wird. Wenn ich später an ein klareres Beispiel denke, tausche ich es aus.
Karl Knechtel
1
Übrigens |für Mengen ist ein Pendleroperator, +für Listen jedoch nicht. Aus diesem Grund denke ich nicht, dass das Argument über Typmehrdeutigkeit besonders stark ist. Da der Bediener nicht pendelt, warum sollte er dasselbe für Typen verlangen? Man könnte einfach zustimmen, dass das Ergebnis den Typ des lhs hat. Auf der anderen Seite wird list + iteratorder Entwickler durch Einschränkung aufgefordert, seine Absichten genauer zu formulieren. Wenn Sie eine neue Liste erstellen möchten, die das Material enthält, das aum das Material erweitert wurde, bgibt es bereits eine Möglichkeit, dies zu tun : new = a.copy(); new += b. Es ist noch eine Linie, aber kristallklar.
a_guest
Der Grund, warum a += bsich anders verhält als a = a + bnicht Effizienz ist. Es ist tatsächlich so, dass Guido das gewählte Verhalten als weniger verwirrend ansah. Stellen Sie sich eine Funktion vor, die eine Liste aals Argument empfängt und dann ausführt a += [1, 2, 3]. Diese Syntax sieht sicherlich so aus, als würde sie die vorhandene Liste ändern, anstatt eine neue Liste zu erstellen. Daher wurde die Entscheidung getroffen, dass sie sich entsprechend der Intuition der meisten Menschen über das erwartete Ergebnis verhalten soll. Der Mechanismus musste jedoch auch für unveränderliche Typen wie ints funktionieren , was zum aktuellen Design führte.
Sven Marnach
Ich persönlich denke , dass das Design tatsächlich mehr verwirrend als einfach macht a += beine Abkürzung für a = a + b, wie Ruby - tat, aber ich kann verstehen , wie wir dort ankamen.
Sven Marnach
21

Sie sind nicht gleichwertig:

b += (4,)

ist eine Abkürzung für:

b.extend((4,))

während +Listen verkettet, also durch:

b = b + (4,)

Sie versuchen, ein Tupel mit einer Liste zu verknüpfen

Alfasin
quelle
14

Wenn Sie dies tun:

b += (4,)

wird dazu konvertiert:

b.__iadd__((4,)) 

Unter der Haube ruft es auf b.extend((4,)), extendakzeptiert einen Iterator und deshalb funktioniert das auch:

b = [1,2,3]
b += range(2)  # prints [1, 2, 3, 0, 1]

aber wenn Sie dies tun:

b = b + (4,)

wird dazu konvertiert:

b = b.__add__((4,)) 

Nur Listenobjekt akzeptieren.

Charif DZ
quelle
4

Aus den offiziellen Dokumenten für veränderbare Sequenztypen :

s += t
s.extend(t)

sind definiert als:

erstreckt sich smit dem Inhalt vont

Was anders ist als definiert als:

s = s + t    # not equivalent in Python!

Dies bedeutet auch, dass jeder Sequenztyp funktioniertt , einschließlich eines Tupels wie in Ihrem Beispiel.

Es funktioniert aber auch für Reichweiten und Generatoren! Zum Beispiel können Sie auch Folgendes tun:

s += range(3)
Eichel
quelle
3

Die "erweiterten" Zuweisungsoperatoren wie +=wurden in Python 2.0 eingeführt, das im Oktober 2000 veröffentlicht wurde. Das Design und die Begründung sind in PEP 203 beschrieben . Eines der erklärten Ziele dieser Betreiber war die Unterstützung des Vor-Ort-Betriebs. Schreiben

a = [1, 2, 3]
a += [4, 5, 6]

soll die Liste a an Ort und Stelle aktualisieren . Dies ist wichtig, wenn andere Verweise auf die Liste vorhanden sind a, z. B. wann aals Funktionsargument empfangen wurde.

Die Operation kann jedoch nicht immer an Ort und Stelle ausgeführt werden, da viele Python-Typen, einschließlich Ganzzahlen und Zeichenfolgen, unveränderlich sind , sodass z. B. i += 1eine Ganzzahl imöglicherweise nicht an Ort und Stelle ausgeführt werden kann.

Zusammenfassend sollte gesagt werden, dass erweiterte Zuweisungsoperatoren nach Möglichkeit an Ort und Stelle arbeiten und ansonsten ein neues Objekt erstellen sollten. Um diese Entwurfsziele zu vereinfachen, wurde der Ausdruck x += yso angegeben, dass er sich wie folgt verhält:

  • Wenn x.__iadd__definiert, x.__iadd__(y)wird ausgewertet.
  • Andernfalls wird, wenn x.__add__implementiert, x.__add__(y)ausgewertet.
  • Andernfalls wird, wenn y.__radd__implementiert, y.__radd__(x)ausgewertet.
  • Andernfalls wird ein Fehler ausgelöst.

Das erste Ergebnis, das durch diesen Prozess erhalten wird, wird zurück zugewiesen x(es sei denn, dieses Ergebnis ist der NotImplementedSingleton. In diesem Fall wird die Suche mit dem nächsten Schritt fortgesetzt).

Dieser Prozess ermöglicht die Implementierung von Typen, die direkte Änderungen unterstützen __iadd__(). Typen, die keine direkte Änderung unterstützen, müssen keine neuen magischen Methoden hinzufügen, da Python automatisch auf im Wesentlichen zurückgreift x = x + y.

Kommen wir nun zu Ihrer eigentlichen Frage: Warum können Sie einer Liste mit einem erweiterten Zuweisungsoperator ein Tupel hinzufügen? Aus dem Speicher war der Verlauf ungefähr so: Die list.__iadd__()Methode wurde implementiert, um einfach die bereits vorhandene list.extend()Methode in Python 2.0 aufzurufen . Bei der Einführung von Iteratoren in Python 2.1 wurde die list.extend()Methode aktualisiert, um beliebige Iteratoren zu akzeptieren. Das Endergebnis dieser Änderungen war, dass my_list += my_tupleab Python 2.1 funktioniert wurde. Die list.__add__()Methode sollte jedoch niemals beliebige Iteratoren als rechtes Argument unterstützen - dies wurde für eine stark typisierte Sprache als unangemessen angesehen.

Ich persönlich denke, dass die Implementierung erweiterter Operatoren in Python etwas zu komplex war. Es hat viele überraschende Nebenwirkungen, zB dieser Code:

t = ([42], [43])
t[0] += [44]

Die zweite Zeile erhöht TypeError: 'tuple' object does not support item assignment, aber die Operation erfolgreich ohnehin durchgeführt - twird ([42, 44], [43])nach der Zeile ausgeführt werden, die den Fehler auslöst.

Sven Marnach
quelle
Bravo! Ein Verweis auf das PEP ist besonders nützlich. Ich habe am anderen Ende einen Link zu einer früheren SO-Frage zum Verhalten der Liste in Tupeln hinzugefügt. Wenn ich zurückblicke, wie Python vor 2.3 oder so war, scheint es im Vergleich zu heute praktisch unbrauchbar zu sein ... (und ich habe immer noch eine vage Erinnerung daran, wie ich versucht habe, 1.5 dazu zu bringen, etwas Nützliches auf einem sehr alten Mac zu tun)
Karl Knechtel
2

Die meisten Leute würden erwarten, dass X + = Y gleich X = X + Y ist. In der Python Pocket Reference (4. Ausgabe) von Mark Lutz heißt es auf Seite 57: "Die folgenden zwei Formate sind ungefähr gleichwertig: X = X + Y, X + = Y ". Die Personen, die Python angegeben haben, haben sie jedoch nicht gleichwertig gemacht. Möglicherweise war dies ein Fehler, der dazu führen wird, dass frustrierte Programmierer stundenlang debuggen, solange Python verwendet wird, aber jetzt ist Python genau so. Wenn X ein veränderlicher Sequenztyp ist, entspricht X + = Y X.extend (Y) und nicht X = X + Y.

zizzler
quelle
> Möglicherweise war dies ein Fehler, der dazu führen wird, dass frustrierte Programmierer stundenlang debuggen, solange Python verwendet wird. <- Haben Sie tatsächlich darunter gelitten? Sie scheinen aus Erfahrung zu sprechen. Ich würde sehr gerne Ihre Geschichte hören.
Veky
1

Da es erklärt hier , wenn arraynicht implementiert __iadd__Verfahren, das b+=(4,)wäre nur ein Shorthanded von , b = b + (4,)aber es ist offensichtlich nicht, so arraytut implementieren __iadd__Methode. Anscheinend ist die Implementierung der __iadd__Methode ungefähr so:

def __iadd__(self, x):
    self.extend(x)

Wir wissen jedoch, dass der obige Code nicht die eigentliche Implementierung der __iadd__Methode ist, aber wir können annehmen und akzeptieren, dass es so etwas wie eine extendMethode gibt, die tuppleEingaben akzeptiert .

Hamidreza
quelle