Когда "i + = x" отличается от "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 ссылаются на один и тот же объект, когда я использую += на 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__ или __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)

Пока 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]

Ответ 4

Если вы просто имеете дело с литералами, то i += 1 имеет такое же поведение, как i = i + 1.

Ответ 5

Простыми словами у вас есть два случая:

i = i + 1

Это создает новую переменную i, использующую значение предыдущего я и увеличивая ее на единицу и сохраняя ее в некоторой другой ячейке памяти.

i += 1

Это не создает новую переменную, а увеличивает значение переменной я в той же ячейке памяти. Это намного эффективнее предыдущего.