@AshwiniChaudhary To całkiem subtelne rozróżnienie, biorąc pod uwagę, że tak i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]jest True. Wielu programistów może nie zauważyć id(i)zmian dla jednej operacji, ale nie dla drugiej.
kojiro
1
@kojiro - Chociaż jest to subtelne rozróżnienie, myślę, że jest ważne.
mgilson
@mgilson to ważne, więc czułem, że potrzebuje wyjaśnienia. :)
Z perspektywy API, __iadd__ma być używany do modyfikowania obiektów podlegających mutacji w miejscu (zwracanie zmutowanego obiektu), podczas gdy __add__powinien zwracać nową instancję czegoś. W przypadku obiektów niezmiennych obie metody zwracają nową instancję, ale __iadd__umieszczą nową instancję w bieżącej przestrzeni nazw o tej samej nazwie, co stara instancja. Dlatego
i =1
i +=1
wydaje się zwiększać i. W rzeczywistości dostajesz nową liczbę całkowitą i przypisujesz ją „na wierzchu” i- tracąc jedno odniesienie do starej liczby całkowitej. W tym przypadku i += 1jest dokładnie taki sam jak i = i + 1. Ale w przypadku większości zmiennych obiektów jest to inna historia:
Jako konkretny przykład:
a =[1,2,3]
b = a
b +=[1,2,3]print a #[1, 2, 3, 1, 2, 3]print b #[1, 2, 3, 1, 2, 3]
w porównaniu do:
a =[1,2,3]
b = a
b = b +[1,2,3]print a #[1, 2, 3]print b #[1, 2, 3, 1, 2, 3]
Zauważ, jak w pierwszym przykładzie, ponieważ bi aodwoływać się do tego samego obiektu, kiedy używam +=na b, to faktycznie zmienia b(i awidzi, że zmiany też - Po tym wszystkim, to odwołuje się do tej samej listy). Jednak w drugim przypadku, gdy to zrobię b = b + [1, 2, 3], pobiera listę, która bsię do niej odwołuje, i łączy ją z nową listą [1, 2, 3]. Następnie przechowuje skonkatenowaną listę w bieżącej przestrzeni nazw jako b- Bez względu na bpoprzednią linię.
1 W wyrażeniu x + y, jeśli x.__add__nie jest realizowany lub jeśli x.__add__(y)powróci NotImplementedi xi ymają różne rodzaje , a następnie x + ystara się rozmowy y.__radd__(x). Tak więc w przypadku, gdy masz
foo_instance += bar_instance
jeśli Foosię nie implementuje __add__lub __iadd__wynik tutaj jest taki sam jak
2 W wyrażeniu foo_instance + bar_instance, bar_instance.__radd__będzie próbował wcześniej foo_instance.__add__czy typ bar_instancejest podklasą typu foo_instance(na przykład issubclass(Bar, Foo)). Racjonalne jest to, bo Barjest w pewnym sensie „wyższy poziom” obiekt niż Footak Barpowinny uzyskać możliwość nadrzędnymi Foo„s zachowanie.
Cóż, +=nazywa __iadd__jeśli istnieje , i wraca do dodawania i ponownego wiązania inaczej. Dlatego i = 1; i += 1działa, chociaż nie ma int.__iadd__. Ale poza tym drobną nitką, świetne wyjaśnienia.
abarnert
4
@abarnert - zawsze zakładałem, że int.__iadd__właśnie zadzwoniłem __add__. Cieszę się, że nauczyłem się dziś czegoś nowego :).
mgilson
@abarnert - Przypuszczam, że może być pełna , x + ypołączeń y.__radd__(x)jeśli x.__add__nie istnieje (lub zwrotów NotImplementedi xi ysą różnych typów)
mgilson
Jeśli naprawdę chcesz być kompletnym, musisz wspomnieć, że bit „jeśli istnieje” przechodzi przez zwykłe mechanizmy getattr, z wyjątkiem niektórych dziwactw z klasycznymi klasami, a dla typów zaimplementowanych w C API szuka zamiast tego nb_inplace_addlub sq_inplace_concatte funkcje C API mają bardziej rygorystyczne wymagania niż metody dundera Pythona i… Ale nie sądzę, żeby to miało znaczenie w odpowiedzi. Główne rozróżnienie polega na tym +=, że próbuje zrobić coś w miejscu, zanim wrócisz do działania w podobny sposób +, co myślę, że już wyjaśniłeś.
abarnert
Tak, przypuszczam, że masz rację ... Chociaż mogę po prostu powrócić do stanowiska, że API C nie jest częścią Pythona . Jest częścią Cpython :-P
mgilson
67
Pod przykryciem i += 1robi coś takiego:
try:
i = i.__iadd__(1)exceptAttributeError:
i = i.__add__(1)
Chociaż i = i + 1robi coś takiego:
i = i.__add__(1)
Jest to niewielkie nadmierne uproszczenie, ale pojawia się pomysł: Python daje rodzajom specjalne sposoby obsługi +=, tworząc zarówno __iadd__metodę, jak i metodę __add__.
Chodzi o to, że typy zmienne, takie jak list, mutują się __iadd__(a następnie wracają self, chyba że robisz coś bardzo trudnego), podczas gdy typy niezmienne, takie jakint , po prostu tego nie implementują.
Na przykład:
>>> l1 =[]>>> l2 = l1
>>> l1 +=[3]>>> l2
[3]
Ponieważ l2jest to ten sam obiekt, co l1i zmutowaliście l1, zmutowaliście równieżl2 .
Ale:
>>> l1 =[]>>> l2 = l1
>>> l1 = l1 +[3]>>> l2
[]
Tutaj nie mutowałeś l1; zamiast tego utworzyłeś nową listę l1 + [3]i odbij nazwę, l1aby na nią wskazywać, pozostawiającl2 wskazywanie na oryginalnej liście.
(W +=wersji ponownie wiązałeś l1, po prostu w takim przypadku wiązałeś go z tym samym, z listktórym był już związany, więc zwykle możesz zignorować tę część).
czy __iadd__faktycznie zadzwonić __add__w razie AttributeError?
mgilson
Cóż, i.__iadd__nie dzwoni __add__; to i += 1to wzywa __add__.
abarnert
errr ... Tak, o to mi chodziło. Ciekawy. Nie wiedziałem, że zrobiono to automatycznie.
mgilson
3
Pierwsza próba jest w rzeczywistości i = i.__iadd__(1)- iaddmoże zmodyfikować obiekt w miejscu, ale nie musi, więc oczekuje się, że zwróci wynik w obu przypadkach.
lvc
Należy pamiętać, że oznacza to, że operator.iaddrozmowy __add__na temat AttributeError, ale nie może on ponownie powiązać wynik ... więc i=1; operator.iadd(i, 1)zwraca 2 i liści iustawiona 1. Co jest nieco mylące.
abarnert
6
Oto przykład, który bezpośrednio porównuje i += xz i = i + x:
def foo(x):
x = x +[42]def bar(x):
x +=[42]
c =[27]
foo(c);# c is not changed
bar(c);# c is changed to [27, 42]
+=
zachowuje się jakextend()
w przypadku list.i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]
jestTrue
. Wielu programistów może nie zauważyćid(i)
zmian dla jednej operacji, ale nie dla drugiej.Odpowiedzi:
Zależy to całkowicie od obiektu
i
.+=
wywołuje__iadd__
metodę (jeśli istnieje - wycofuje się,__add__
jeśli nie istnieje), podczas gdy+
wywołuje__add__
metodę 1 lub__radd__
metodę w kilku przypadkach 2 .Z perspektywy API,
__iadd__
ma być używany do modyfikowania obiektów podlegających mutacji w miejscu (zwracanie zmutowanego obiektu), podczas gdy__add__
powinien zwracać nową instancję czegoś. W przypadku obiektów niezmiennych obie metody zwracają nową instancję, ale__iadd__
umieszczą nową instancję w bieżącej przestrzeni nazw o tej samej nazwie, co stara instancja. Dlategowydaje się zwiększać
i
. W rzeczywistości dostajesz nową liczbę całkowitą i przypisujesz ją „na wierzchu”i
- tracąc jedno odniesienie do starej liczby całkowitej. W tym przypadkui += 1
jest dokładnie taki sam jaki = i + 1
. Ale w przypadku większości zmiennych obiektów jest to inna historia:Jako konkretny przykład:
w porównaniu do:
Zauważ, jak w pierwszym przykładzie, ponieważ
b
ia
odwoływać się do tego samego obiektu, kiedy używam+=
nab
, to faktycznie zmieniab
(ia
widzi, że zmiany też - Po tym wszystkim, to odwołuje się do tej samej listy). Jednak w drugim przypadku, gdy to zrobięb = b + [1, 2, 3]
, pobiera listę, którab
się do niej odwołuje, i łączy ją z nową listą[1, 2, 3]
. Następnie przechowuje skonkatenowaną listę w bieżącej przestrzeni nazw jakob
- Bez względu nab
poprzednią linię.1 W wyrażeniu
x + y
, jeślix.__add__
nie jest realizowany lub jeślix.__add__(y)
powróciNotImplemented
ix
iy
mają różne rodzaje , a następniex + y
stara się rozmowyy.__radd__(x)
. Tak więc w przypadku, gdy maszfoo_instance += bar_instance
jeśli
Foo
się nie implementuje__add__
lub__iadd__
wynik tutaj jest taki sam jakfoo_instance = bar_instance.__radd__(bar_instance, foo_instance)
2 W wyrażeniu
foo_instance + bar_instance
,bar_instance.__radd__
będzie próbował wcześniejfoo_instance.__add__
czy typbar_instance
jest podklasą typufoo_instance
(na przykładissubclass(Bar, Foo)
). Racjonalne jest to, boBar
jest w pewnym sensie „wyższy poziom” obiekt niżFoo
takBar
powinny uzyskać możliwość nadrzędnymiFoo
„s zachowanie.źródło
+=
nazywa__iadd__
jeśli istnieje , i wraca do dodawania i ponownego wiązania inaczej. Dlategoi = 1; i += 1
działa, chociaż nie maint.__iadd__
. Ale poza tym drobną nitką, świetne wyjaśnienia.int.__iadd__
właśnie zadzwoniłem__add__
. Cieszę się, że nauczyłem się dziś czegoś nowego :).x + y
połączeńy.__radd__(x)
jeślix.__add__
nie istnieje (lub zwrotówNotImplemented
ix
iy
są różnych typów)nb_inplace_add
lubsq_inplace_concat
te funkcje C API mają bardziej rygorystyczne wymagania niż metody dundera Pythona i… Ale nie sądzę, żeby to miało znaczenie w odpowiedzi. Główne rozróżnienie polega na tym+=
, że próbuje zrobić coś w miejscu, zanim wrócisz do działania w podobny sposób+
, co myślę, że już wyjaśniłeś.Pod przykryciem
i += 1
robi coś takiego:Chociaż
i = i + 1
robi coś takiego:Jest to niewielkie nadmierne uproszczenie, ale pojawia się pomysł: Python daje rodzajom specjalne sposoby obsługi
+=
, tworząc zarówno__iadd__
metodę, jak i metodę__add__
.Chodzi o to, że typy zmienne, takie jak
list
, mutują się__iadd__
(a następnie wracająself
, chyba że robisz coś bardzo trudnego), podczas gdy typy niezmienne, takie jakint
, po prostu tego nie implementują.Na przykład:
Ponieważ
l2
jest to ten sam obiekt, col1
i zmutowaliściel1
, zmutowaliście równieżl2
.Ale:
Tutaj nie mutowałeś
l1
; zamiast tego utworzyłeś nową listęl1 + [3]
i odbij nazwę,l1
aby na nią wskazywać, pozostawiającl2
wskazywanie na oryginalnej liście.(W
+=
wersji ponownie wiązałeśl1
, po prostu w takim przypadku wiązałeś go z tym samym, zlist
którym był już związany, więc zwykle możesz zignorować tę część).źródło
__iadd__
faktycznie zadzwonić__add__
w razieAttributeError
?i.__iadd__
nie dzwoni__add__
; toi += 1
to wzywa__add__
.i = i.__iadd__(1)
-iadd
może zmodyfikować obiekt w miejscu, ale nie musi, więc oczekuje się, że zwróci wynik w obu przypadkach.operator.iadd
rozmowy__add__
na tematAttributeError
, ale nie może on ponownie powiązać wynik ... więci=1; operator.iadd(i, 1)
zwraca 2 i liścii
ustawiona1
. Co jest nieco mylące.Oto przykład, który bezpośrednio porównuje
i += x
zi = i + x
:źródło