Мне сказали, что += может иметь разные эффекты, чем стандартная нотация i = i +. Есть ли случай, когда i += 1 будет отличаться от i = i + 1?
Когда "i + = x" отличается от "i = я + x" в Python?
Ответ 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, он фактически меняет b (и a видит, что это тоже изменение) - В конце концов, он ссылается на тот же список). Во втором случае, однако, когда я делаю 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
Это не создает новую переменную, а увеличивает значение переменной я в той же ячейке памяти. Это намного эффективнее предыдущего.