+=
Operator w python zdaje się nieoczekiwanie działa na listach. Czy ktoś może mi powiedzieć, co się tutaj dzieje?
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
WYNIK
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar
wydaje się wpływać na każdą instancję klasy, podczas gdy foo = foo + bar
wydaje się , że zachowuje się w sposób, jakiego oczekiwałbym od rzeczy.
+=
Operatora nazywana jest „związek operatorowi przypisanie”.
python
augmented-assignment
eukalculia
źródło
źródło
+
operatora na tablicach. Myślę, że w tym przypadku ma to sens+=
.Odpowiedzi:
Ogólna odpowiedź jest taka
+=
, że próbuje wywołać__iadd__
metodę specjalną, a jeśli ta nie jest dostępna, próbuje użyć__add__
zamiast niej. Tak więc problem polega na różnicy między tymi specjalnymi metodami.__iadd__
Specjalna metoda jest dla dodatku w miejscu, to jest to obiekt, który mutuje działa dalej.__add__
Szczególny sposób powraca nowego przedmiotu i jest wykorzystywany do standardowej+
operatora.Więc kiedy
+=
operator jest używany na obiekcie, który ma__iadd__
zdefiniowane, obiekt jest modyfikowany w miejscu. W przeciwnym razie spróbuje użyć zwykłego__add__
i zwróci nowy obiekt.Dlatego dla typów zmiennych, takich jak listy,
+=
zmienia się wartość obiektu, podczas gdy dla typów niezmiennych, takich jak krotki, ciągi znaków i liczby całkowite, zamiast tego zwracany jest nowy obiekt (a += b
staje się równoważnya = a + b
).Dla typów że wsparcie zarówno
__iadd__
i__add__
ty dlatego trzeba być ostrożnym, który z nich korzystać.a += b
będzie dzwonić__iadd__
i mutowaća
, podczas gdya = a + b
stworzy nowy obiekt i przypisze go doa
. To nie ta sama operacja!Dla typów niezmiennych (gdzie nie masz
__iadd__
)a += b
ia = a + b
są równoważne. To właśnie pozwala ci używać+=
na niezmiennych typach, co może wydawać się dziwną decyzją projektową, dopóki nie uznasz, że w przeciwnym razie nie możesz użyć+=
na niezmiennych typach, takich jak liczby!źródło
__radd__
metoda, która może być czasami wywoływana (ma to znaczenie dla wyrażeń, które obejmują głównie podklasy).+=
faktycznie rozszerza listę, to wyjaśnia, dlaczegox = []; x = x + {}
dajeTypeError
chwilęx = []; x += {}
po prostu wraca[]
.W ogólnym przypadku, zobacz odpowiedź Scotta Griffitha . Jednak w przypadku list takich jak ty
+=
operator jest skrótem odsomeListObject.extend(iterableObject)
. Zobacz dokumentację rozszerzenia () .extend
Funkcja dołączania wszystkich elementów parametr do listy.Kiedy
foo += something
modyfikujesz listęfoo
w miejscu, nie zmieniasz odniesienia, na którefoo
wskazuje nazwa , ale bezpośrednio zmieniasz obiekt listy. Wfoo = foo + something
rzeczywistości tworzysz nową listę.Ten przykładowy kod wyjaśni to:
Zwróć uwagę, jak zmienia się odniesienie po ponownym przypisaniu nowej listy do
l
.Ponieważ
bar
jest to zmienna klasy, a nie zmienna instancji, modyfikacja lokalna wpłynie na wszystkie instancje tej klasy. Jednak podczas redefiniowaniaself.bar
instancja będzie miała oddzielną zmienną instancjiself.bar
bez wpływu na inne instancje klas.źródło
a += b
różni się to oda = a + b
dwóch lista
ib
. Ale to ma sens;extend
częściej będzie to zamierzone do zrobienia z listami, zamiast tworzyć nową kopię całej listy, która będzie miała większą złożoność czasową. Jeśli programiści muszą uważać, aby nie modyfikować oryginalnych list na miejscu, krotki są lepszą opcją jako niezmienne obiekty.+=
z krotkami nie może modyfikować oryginalnej krotki.Problem polega na tym, że
bar
jest definiowany jako atrybut klasy, a nie zmienna instancji.W
foo
programie atrybut klasy jest modyfikowany winit
metodzie, dlatego dotyczy to wszystkich instancji.W programie
foo2
zmienna instancji jest definiowana przy użyciu (pustego) atrybutu klasy, a każda instancja otrzymuje własnąbar
.„Prawidłowa” implementacja to:
Oczywiście atrybuty klas są całkowicie legalne. W rzeczywistości możesz uzyskać do nich dostęp i modyfikować je bez tworzenia instancji klasy w następujący sposób:
źródło
W grę wchodzą dwie rzeczy:
+
operator wywołuje__add__
metodę z listy. Pobiera wszystkie elementy ze swoich operandów i tworzy nową listę zawierającą te elementy, zachowując ich kolejność.+=
__iadd__
metoda wywołania operatora na liście. Pobiera iterowalne i dołącza wszystkie elementy iterowalne do listy w miejscu. Nie tworzy nowego obiektu listy.W klasie
foo
instrukcjaself.bar += [x]
nie jest instrukcją przypisania, ale w rzeczywistości jest tłumaczeniemktóry modyfikuje listę w miejscu i działa jak metoda list
extend
.W klasie
foo2
wręcz przeciwnie, instrukcja przypisania winit
metodziemożna zdekonstruować jako:
Instancja nie ma atrybutu
bar
(istnieje jednak atrybut klasy o tej samej nazwie), więc uzyskuje dostęp do atrybutu klasybar
i tworzy nową listę, dołączającx
do niej. Oświadczenie przekłada się na:Następnie tworzy atrybut instancji
bar
i przypisuje do niego nowo utworzoną listę. Zauważ, żebar
prawa strona zadania różni się odbar
prawej strony.Dla instancji klasy
foo
,bar
jest atrybutem klasy i nie atrybut instancji. W związku z tym każda zmiana atrybutu klasybar
zostanie odzwierciedlona we wszystkich instancjach.Wręcz przeciwnie, każda instancja klasy
foo2
ma swój własny atrybut instancji,bar
który różni się od atrybutu klasy o tej samej nazwiebar
.Mam nadzieję, że to wszystko wyjaśnia.
źródło
Chociaż minęło dużo czasu i powiedziano wiele poprawnych rzeczy, nie ma odpowiedzi, która łączy oba efekty.
Masz 2 efekty:
+=
(jak stwierdził Scott Griffiths )W klasie
foo
The__init__
metoda modyfikuje atrybutu class. To dlatego, żeself.bar += [x]
przekłada się naself.bar = self.bar.__iadd__([x])
.__iadd__()
służy do modyfikacji w miejscu, więc modyfikuje listę i zwraca do niej odniesienie.Należy zauważyć, że dyktando instancji jest modyfikowane, chociaż normalnie nie byłoby to konieczne, ponieważ dyktando klasy zawiera już to samo przypisanie. Więc ten szczegół pozostaje prawie niezauważony - z wyjątkiem sytuacji, gdy zrobisz to
foo.bar = []
później. Tutaj instancjebar
pozostają niezmienione dzięki wspomnianemu faktowi.W klasie
foo2
, jednak klasa użytkownikabar
jest używana, ale nie dotknął. Zamiast tego[x]
dodawane jest do niego a, tworząc nowy obiekt, jakself.bar.__add__([x])
nazywamy go tutaj, który nie modyfikuje obiektu. Wynik jest następnie umieszczany w dict instancji, dając instancji nową listę jako dict, podczas gdy atrybut klasy pozostaje zmodyfikowany.Rozróżnienie między
... = ... + ...
i... += ...
dotyczy również późniejszych zadań:Możesz zweryfikować tożsamość obiektów za pomocą
print id(foo), id(f), id(g)
(nie zapomnij o dodatkowych()
s, jeśli korzystasz z Python3).BTW:
+=
Operator jest nazywany „przypisaniem rozszerzonym” i ogólnie jest przeznaczony do wykonywania modyfikacji w miejscu, o ile to możliwe.źródło
Wydaje się, że inne odpowiedzi w dużej mierze ją obejmują, chociaż wydaje się, że warto zacytować i odnieść się do Rozszerzonego Zadania PEP 203 :
...
źródło
źródło
Widzimy, że kiedy próbujemy zmodyfikować niezmienny obiekt (w tym przypadku liczbę całkowitą), Python po prostu daje nam inny obiekt. Z drugiej strony jesteśmy w stanie wprowadzić zmiany w zmiennym obiekcie (liście) i pozostawić go tym samym obiektem przez cały czas.
ref: https://medium.com/@tyastropheus/tricky-python-i-memory-management-for-mutable-immutable-objects-21507d1e5b95
Zapoznaj się również z poniższym adresem URL, aby zrozumieć płytką i głęboką kopię
https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/
źródło