“İ + = x”, Python'daki “i = i + x” den ne zaman farklıdır?


212

Bunun +=standart gösteriminden farklı etkileri olabileceği söylendi i = i +. i += 1Farklı olacak bir durum var mı i = i + 1?


7
+=extend()listelerdeki gibi davranır .
Ashwini Chaudhary

12
@AshwiniChaudhary Bu oldukça ince ayrım düşünüyor, var i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]olan True. Birçok geliştirici, id(i)bir işlem için bu değişikliklerin farkına varamayabilir, diğerinde değil.
kojiro

1
@kojiro - İnce bir ayrım olsa da, bence önemli bir ayrım.
mgilson

mgilson önemlidir ve bu yüzden bir açıklamaya ihtiyacı olduğunu hissettim. :)
kojiro

1
Java'daki ikisi arasındaki farklar ile ilgili soru: stackoverflow.com/a/7456548/245966
jakub.g

Yanıtlar:


317

Bu tamamen nesneye bağlıdır i.

+=aramaları __iadd__yöntemi (varsa - ile geri düşme __add__mevcut değil ise) ise +aramalar __add__yöntem 1 veya __radd__bazı durumlarda yöntem 2 .

Bir API perspektifinden bakıldığında, mutasyona uğramış __iadd__nesneleri yerinde değiştirmek (mutasyona uğramış nesneyi döndürmek) için kullanılırken, __add__bir şeyin yeni bir örneğini döndürmek gerekir . İçin değişmez nesneler, her iki yöntem yeni bir örnek dönmek fakat __iadd__eski örneği vardı, aynı adla geçerli ad alanında yeni bir örneğini koyacağız. Bu nedenle

i = 1
i += 1

artmış gibi görünüyor i. Gerçekte, yeni bir tamsayı alır ve bunu "üstüne" atarsınız i- eski tamsayıya bir referans kaybedersiniz. Bu durumda, i += 1tam olarak aynıdır i = i + 1. Ancak, değişebilir nesnelerle, bu farklı bir hikaye:

Somut bir örnek olarak:

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]

nazaran:

a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]

ihbar nasıl ilk örnekte, çünkü bve aaynı nesneye başvuran, ben kullandığınızda +=üzerinde b, aslında değişir b(ve açok bu değişikliği görür - Sonuçta, aynı listeyi referans oluyor). Ancak ikinci durumda, bunu yaptığımda b = b + [1, 2, 3], bbaşvurulan listeyi alır ve yeni bir listeyle birleştirir [1, 2, 3]. Daha sonra sıralı listeyi geçerli ad alanında b- Daha bönce satırın ne olduğuna bakmaksızın - olarak saklar .


1 ifadede x + y, eğer x.__add__uygulanmadı veya eğer x.__add__(y)döner NotImplemented ve xve yfarklı türleri vardır , o zaman x + yçağrı çalışır y.__radd__(x). Yani, sahip olduğunuz durumda

foo_instance += bar_instance

eğer Foouygulanmazsa __add__veya __iadd__o zaman buradaki sonuç aynı

foo_instance = bar_instance.__radd__(bar_instance, foo_instance)

2 ifadesinde foo_instance + bar_instance, bar_instance.__radd__yargılanabileceklerdir foo_instance.__add__ halinde tipi bar_instancetürü bir alt sınıfı foo_instance(örneğin issubclass(Bar, Foo)). Çünkü bunun için rasyonel olan Bardaha "üst düzey" nesne Bir anlamda olduğu Fooböylece Bargeçersiz kılma seçeneği almalısınız Foo'ın davranışı.


18
Eh, +=çağıran __iadd__ varsa ve ekleme ve başka türlü yeniden bağlama geri düşer. Bu yüzden i = 1; i += 1yok olmasına rağmen çalışır int.__iadd__. Ama bu küçük nit dışında, büyük açıklamalar.
abarnert

4
@abarnert - Hep farz int.__iadd__aradı __add__. Bugün yeni bir şeyler öğrendiğim için mutluyum :).
mgilson

@abarnert - Öyle bir belki varsayalım komple , x + yaramaları y.__radd__(x)durumunda x.__add__(veya döner yoksa NotImplementedve xve yfarklı türde olan)
mgilson

Gerçekten tamamlayıcı olmak istiyorsanız, "varsa" bitinin klasik sınıflarla bazı tuhaflıklar dışında normal getattr mekanizmalarından geçtiğini ve bunun yerine C API'sinde uygulanan türler için şunu belirtmeniz gerekir: nb_inplace_addveya sq_inplace_concat, ve bu C API işlevleri Python dunder yöntemlerinden daha katı gereksinimleri vardır, ve… Ama ben bunun cevabı ile ilgili olduğunu sanmıyorum. Asıl ayrım, daha önce açıkladığınızı düşünüyorum +=gibi hareket etmeye geri dönmeden önce yerinde bir ekleme yapmaya çalışıyor +.
abarnert

Evet, sanırım haklısın ... Her ne kadar C API'sinin python'un bir parçası olmadığı fikrine düşebilsem de . Cpython'un bir parçası :-P
mgilson

67

Kapakların altında, i += 1böyle bir şey yapar:

try:
    i = i.__iadd__(1)
except AttributeError:
    i = i.__add__(1)

Buna i = i + 1benzer bir şey yaparken :

i = i.__add__(1)

Bu hafif bir aşırı basitleştirmedir, ancak fikri anlarsınız: Python, türlere +=bir __iadd__yöntem de oluşturarak özel olarak işlemek için bir yol verir __add__.

Amaç, değişebilir türlerin, örneğin list, mutasyona uğramış türler, onu uygulamamalarına rağmen, kendilerini mutasyona uğratacak __iadd__(ve sonra selfçok zor bir şey yapmazsanız geri dönecektir ) int.

Örneğin:

>>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3]

Çünkü l2aynı nesne l1ve siz mutasyona uğradınız l1, siz de mutasyona uğradınız l2.

Fakat:

>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]

Burada mutasyona uğramadın l1; bunun yerine, yeni bir liste oluşturulur l1 + [3]ve adını ribaund l1bırakarak ona noktaya l2orijinal listeye işaret.

(İçinde += Sürümde de yeniden hatırlıyorsunuz l1, sadece bu durumda listzaten bağlı olduğu aynı şekilde yeniden bağlıyorsunuz , bu yüzden genellikle bu kısmı göz ardı edebilirsiniz.)


yok __iadd__aslında diyoruz __add__bir durumunda AttributeError?
mgilson

Peki, i.__iadd__aramaz __add__; işte i += 1bu __add__.
abarnert

errr ... Evet, demek istediğim buydu. İlginç. Bunun otomatik olarak yapıldığını fark etmedim.
mgilson

3
İlk girişim aslında i = i.__iadd__(1)- iadd yapabilirsiniz yerinde nesneyi değiştirmek, ancak zorunlu değildir ve bu yüzden her iki durumda da sonuç dönmesi bekleniyor.
lvc

Bu araçlar o Not operator.iaddçağrıları __add__üzerine AttributeError, ancak sonuç rebind edemez ... bu yüzden i=1; operator.iadd(i, 1)dönene 2 ve yaprakların iayarlı 1. Bu biraz kafa karıştırıcı.
abarnert

6

Burada direkt olarak karşılaştıran bir örnek i += xile 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]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.