Мне сказали, что +=
может иметь разные эффекты, чем стандартная нотация 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
Это не создает новую переменную, а увеличивает значение переменной я в той же ячейке памяти. Это намного эффективнее предыдущего.