Вопрос-Ответ

When is "i += x" different from "i = i + x" in Python?

Когда "i + = x" отличается от "i = i + x" в Python?

Мне сказали, что += может иметь эффекты, отличные от стандартных обозначений i = i +. Есть ли случай, в котором i += 1 будет отличаться от i = i + 1?

Переведено автоматически
Ответ 1

Это полностью зависит от объекта i.

+= вызывает __iadd__ метод (если он существует - возвращаясь к __add__, если он не существует), тогда как + вызывает __add__ метод1 или __radd__ метод в некоторых случаях2.

С точки зрения API, __iadd__ предполагается использовать для модификации изменяемых объектов на месте (возвращая объект, который был изменен), тогда как __add__ должен возвращать новый экземпляр чего-либо. Для неизменяемых объектов оба метода возвращают новый экземпляр, но __iadd__ поместят новый экземпляр в текущее пространство имен с тем же именем, что и у старого экземпляра. Вот почему

i = 1
i += 1

кажется, увеличивается i. На самом деле вы получаете новое целое число и присваиваете его "поверх" i - теряя одну ссылку на старое целое число. В этом случае i += 1 точно такой же, как i = i + 1. Но с большинством изменяемых объектов это другая история:

В качестве конкретного примера:

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]

по сравнению с:

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

обратите внимание, как в первом примере, поскольку b и a ссылаются на один и тот же объект, когда я использую += on b, он фактически меняется ba тоже видит это изменение - в конце концов, он ссылается на тот же список). Однако во втором случае, когда я делаю b = b + [1, 2, 3], это берет список, на который b ссылается, и объединяет его с новым списком [1, 2, 3]. Затем он сохраняет объединенный список в текущем пространстве имен как b -- Без учета того, какой b была строка раньше.


1в выражение x + y, если x.__add__ не реализовано или если x.__add__(y) возвращает NotImplemented и x и y имеют разные типы, то x + y пытается дозвониться y.__radd__(x). Итак, в случае, когда у вас есть

foo_instance += bar_instance

если Foo не реализует __add__ or __iadd__ , то результат здесь такой же, как

foo_instance = bar_instance.__radd__(bar_instance, foo_instance)

2В выражении foo_instance + bar_instance, bar_instance.__radd__ будет опробовано ранее, foo_instance.__add__ если тип bar_instance является подклассом типа foo_instance (например, issubclass(Bar, Foo)). Обоснование этого заключается в том, что Bar в некотором смысле является объектом "более высокого уровня", чем Foo поэтому Bar должен получить возможность переопределения Foo поведения.

Ответ 2

Под прикрытием, i += 1 делает что-то вроде этого:

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

While i = i + 1 делает что-то вроде этого:

i = i.__add__(1)

Это небольшое упрощение, но вы поняли идею: Python предоставляет типам способ обработки += особым образом, создавая __iadd__ метод, а также __add__ .

Предполагается, что изменяемые типы, такие как list, будут изменять себя в __iadd__ (и затем возвращать self, если вы не делаете что-то очень сложное), в то время как неизменяемые типы, такие как int, просто не будут это реализовывать.

Например:

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

Поскольку l2 это тот же объект, что и l1, и вы мутировали l1, вы также мутировали l2.

Но:

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

Здесь вы не мутировали l1; вместо этого вы создали новый список, l1 + [3] и изменили имя, l1 чтобы оно указывало на него, оставив l2 указание на исходный список.

+= версии вы также выполняли повторную привязку l1, просто в этом случае вы повторно привязывали его к тому же list, к которому он уже был привязан, поэтому вы обычно можете игнорировать эту часть.)

Ответ 3

Вот пример, который напрямую сравнивает i += x с 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]
python