Der +=
Operator in Python scheint unerwartet mit Listen zu arbeiten. Kann mir jemand sagen, was hier los ist?
class foo:
bar = []
def __init__(self,x):
self.bar += [x]
class foo2:
bar = []
def __init__(self,x):
self.bar = self.bar + [x]
f = foo(1)
g = foo(2)
print f.bar
print g.bar
f.bar += [3]
print f.bar
print g.bar
f.bar = f.bar + [4]
print f.bar
print g.bar
f = foo2(1)
g = foo2(2)
print f.bar
print g.bar
AUSGABE
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar
scheint jede Instanz der Klasse zu betreffen, während foo = foo + bar
so zu verhalten scheint, wie ich es erwarten würde.
Der +=
Operator wird als "zusammengesetzter Zuweisungsoperator" bezeichnet.
python
augmented-assignment
Eukalkulie
quelle
quelle
+
Operator nicht einmal für Arrays verwenden. Ich denke, dass es in diesem Fall durchaus Sinn macht,+=
das anzuhängen.Antworten:
Die allgemeine Antwort lautet, dass
+=
versucht wird, die__iadd__
spezielle Methode aufzurufen , und wenn diese nicht verfügbar ist, wird versucht, sie zu verwenden__add__
stattdessen . Das Problem ist also der Unterschied zwischen diesen speziellen Methoden.Die
__iadd__
spezielle Methode ist für eine In-Place-Addition vorgesehen, dh sie mutiert das Objekt, auf das sie einwirkt. Die__add__
spezielle Methode gibt ein neues Objekt zurück und wird auch für den Standardoperator verwendet+
.Wenn der
+=
Operator für ein Objekt verwendet wird, für das eine__iadd__
Definition vorliegt, wird das Objekt an Ort und Stelle geändert. Andernfalls wird stattdessen versucht, die Ebene zu verwenden__add__
und ein neues Objekt zurückzugeben.Aus diesem Grund
+=
ändert für veränderbare Typen wie Listen der Wert des Objekts, während für unveränderliche Typen wie Tupel, Zeichenfolgen und Ganzzahlen stattdessen ein neues Objekt zurückgegeben wird (a += b
entsprichta = a + b
).Für die Typen , dass die Unterstützung sowohl
__iadd__
und__add__
Sie deshalb sein müssen aufpassen , welche Sie verwenden.a += b
ruft auf__iadd__
und mutierta
, währenda = a + b
ein neues Objekt erstellt und zugewiesen wirda
. Sie sind nicht die gleiche Operation!Für unveränderliche Typen (wo Sie keine haben
__iadd__
)a += b
unda = a + b
gleichwertig sind. Dies ist es, was Sie+=
für unveränderliche Typen verwenden können, was eine seltsame Entwurfsentscheidung sein könnte, bis Sie bedenken, dass Sie es sonst nicht+=
für unveränderliche Typen wie Zahlen verwenden könnten !quelle
__radd__
Methoden, die manchmal aufgerufen werden können (sie sind relevant für Ausdrücke, die hauptsächlich Unterklassen betreffen).+=
tatsächlich erweitert wird , erklärt dies, warumx = []; x = x + {}
eineTypeError
Weilex = []; x += {}
nur zurückkehrt[]
.Für den allgemeinen Fall siehe die Antwort von Scott Griffith . Beim Umgang mit Listen wie Ihnen ist der
+=
Operator jedoch eine Abkürzung fürsomeListObject.extend(iterableObject)
. Siehe die Dokumentation von extens () .Das
extend
Funktion hängt alle Elemente des Parameters an die Liste an.Wenn Sie dies tun, ändern
foo += something
Sie die Listefoo
an Ort und Stelle, sodass Sie nicht die Referenz ändern, auf die der Namefoo
verweist, sondern das Listenobjekt direkt ändern. Mitfoo = foo + something
erstellen Sie tatsächlich eine neue Liste.Dieser Beispielcode erklärt es:
Beachten Sie, wie sich die Referenz ändert, wenn Sie die neue Liste neu zuweisen
l
.Da
bar
es sich um eine Klassenvariable anstelle einer Instanzvariablen handelt, wirkt sich eine Änderung an Ort und Stelle auf alle Instanzen dieser Klasse aus. Bei der Neudefinitionself.bar
verfügt die Instanz jedoch über eine separate Instanzvariable,self.bar
ohne die anderen Klasseninstanzen zu beeinflussen.quelle
a += b
sich das vona = a + b
zwei Listen unterscheideta
undb
. Aber es macht Sinn;extend
Dies ist häufiger beabsichtigt, um mit Listen zu tun, als eine neue Kopie der gesamten Liste zu erstellen, die eine höhere zeitliche Komplexität aufweist. Wenn Entwickler darauf achten müssen, dass sie die ursprünglichen Listen nicht ändern, sind Tupel eine bessere Option, da sie unveränderliche Objekte sind.+=
mit Tupeln kann das ursprüngliche Tupel nicht ändern.Das Problem hierbei ist, dass
bar
es als Klassenattribut definiert ist, nicht als Instanzvariable.In
foo
wird das Klassenattribut in derinit
Methode geändert , weshalb alle Instanzen betroffen sind.In
foo2
wird eine Instanzvariable mithilfe des (leeren) Klassenattributs definiert, und jede Instanz erhält ihre eigenebar
.Die "richtige" Implementierung wäre:
Natürlich sind Klassenattribute völlig legal. Tatsächlich können Sie auf sie zugreifen und sie ändern, ohne eine Instanz der Klasse wie folgt zu erstellen:
quelle
Hier geht es um zwei Dinge:
+
Der Operator ruft die__add__
Methode in einer Liste auf. Es nimmt alle Elemente aus seinen Operanden und erstellt eine neue Liste mit den Elementen, die ihre Reihenfolge beibehalten.+=
Bedieneraufrufe__iadd__
Methode in der Liste auf. Es nimmt eine Iterable und hängt alle Elemente der Iterable an die vorhandene Liste an. Es wird kein neues Listenobjekt erstellt.In der Klasse ist
foo
die Anweisungself.bar += [x]
keine Zuweisungsanweisung, sondern übersetzt tatsächlich inDadurch wird die Liste an Ort und Stelle geändert und es wird die Listenmethode verwendet
extend
.In der Klasse
foo2
dagegen die Zuweisungsanweisung in derinit
Methodekann wie folgt dekonstruiert werden:
Die Instanz hat kein Attribut
bar
(es gibt jedoch ein gleichnamiges Klassenattribut), greift also auf das Klassenattribut zubar
und erstellt eine neue Liste, indem siex
an dieses angehängt wird. Die Aussage übersetzt zu:Anschließend wird ein Instanzattribut erstellt
bar
und die neu erstellte Liste zugewiesen. Beachten Sie, dass sichbar
die Rhs der Zuordnung von der unterscheidetbar
auf den LHS unterscheiden.Für Instanzen der Klasse
foo
,bar
ist ein Klassenattribut und nicht die Instanz - Attribut. Daher wird jede Änderung des Klassenattributsbar
für alle Instanzen berücksichtigt.Im Gegenteil, jede Instanz der Klasse
foo2
hat ein eigenes Instanzattribut,bar
das sich vom gleichnamigen Klassenattribut unterscheidetbar
.Hoffe das klärt die Dinge.
quelle
Obwohl viel Zeit vergangen ist und viele richtige Dinge gesagt wurden, gibt es keine Antwort, die beide Effekte bündelt.
Sie haben 2 Effekte:
+=
(wie von Scott Griffiths angegeben )In der Klasse
foo
, die__init__
modifiziert Methode , um das Klassenattribut. Es ist, weilself.bar += [x]
übersetzt zuself.bar = self.bar.__iadd__([x])
.__iadd__()
dient der Inplace-Änderung, ändert also die Liste und gibt einen Verweis darauf zurück.Beachten Sie, dass das Instanz-Diktat geändert wird, obwohl dies normalerweise nicht erforderlich wäre, da das Klassendiktat bereits dieselbe Zuweisung enthält. Dieses Detail bleibt also fast unbemerkt - außer wenn Sie es
foo.bar = []
später tun . Hier bleiben die Instanzenbar
dank dieser Tatsache gleich.In der Klasse
foo2
wird die Klassebar
jedoch verwendet, aber nicht berührt. Stattdessen wird a[x]
hinzugefügt, um ein neues Objekt zu bilden, wieself.bar.__add__([x])
hier genannt, das das Objekt nicht ändert. Das Ergebnis wird dann in das Instanz-Diktat eingefügt und gibt der Instanz die neue Liste als Diktat, während das Attribut der Klasse geändert bleibt.Die Unterscheidung zwischen
... = ... + ...
und... += ...
wirkt sich auch auf die nachfolgenden Aufgaben aus:Sie können die Identität der Objekte mit überprüfen
print id(foo), id(f), id(g)
(vergessen Sie nicht die zusätzlichen()
s, wenn Sie mit Python3 arbeiten).Übrigens: Der
+=
Bediener wird als "erweiterte Zuweisung" bezeichnet und soll im Allgemeinen so weit wie möglich Änderungen an Ort und Stelle vornehmen.quelle
Die anderen Antworten scheinen es so ziemlich abgedeckt zu haben, obwohl es sich lohnt, die Augmented Assignments PEP 203 zu zitieren und darauf zu verweisen :
...
quelle
quelle
Wir sehen, dass Python uns beim Versuch, ein unveränderliches Objekt (in diesem Fall eine Ganzzahl) zu ändern, stattdessen einfach ein anderes Objekt gibt. Auf der anderen Seite können wir Änderungen an einem veränderlichen Objekt (einer Liste) vornehmen und dafür sorgen, dass es durchgehend dasselbe Objekt bleibt.
Ref: https://medium.com/@tyastropheus/tricky-python-i-memory-management-for-mutable-immutable-objects-21507d1e5b95
Lesen Sie auch die unten stehende URL, um die Flach- und Tiefenkopie zu verstehen
https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/
quelle