Мне сказали, что +=
может иметь другие эффекты, чем стандартное обозначение i = i +
. Есть ли случай, когда i += 1
будет отличаться от i = i + 1
?
Когда i += x отличается от i = i + x в Python?
Ответы (3)
Это полностью зависит от объекта i
.
+=
вызывает метод __iadd__
(если он существует, на __add__
, если он не существует), тогда как +
вызывает метод __add__
a>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
.
+=
вызывает __iadd__
, если он существует, и возвращается к добавлению и повторному связыванию в противном случае. Вот почему i = 1; i += 1
работает, хотя int.__iadd__
нет. Но кроме этой мелкой гниды, отличные объяснения.
- person abarnert; 13.03.2013
int.__iadd__
только что позвонил __add__
. Я рад, что узнал что-то новое сегодня :).
- person mgilson; 13.03.2013
x + y
вызывает y.__radd__(x)
, если x.__add__
не существует (или возвращает NotImplemented
, x
и y
разных типов)
- person mgilson; 13.03.2013
nb_inplace_add
, либо sq_inplace_concat
, и к этим функциям C API предъявляются более строгие требования, чем к методам dunder Python, и… Но я не думаю, что это имеет отношение к ответу. Основное отличие состоит в том, что +=
пытается выполнить добавление на месте, прежде чем вернуться к действию, аналогичному +
, что, я думаю, вы уже объяснили.
- person abarnert; 13.03.2013
x + y
пытается type(y).__radd__
сначала, если issubclass(type(y), type(x))
.
- person wim; 25.01.2017
i
... [.]?
- person Kindred; 22.11.2018
i
-- что бы там ни было ранее, оно будет на один шаг ближе к сборке мусора.
- person mgilson; 29.11.2018
x + y
с одинаковыми типами сначала пытается __add__
.
- person Kelly Bundy; 15.09.2020
Под прикрытием 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
, к которому он уже был привязан, поэтому обычно вы можете игнорировать эту часть.)
__iadd__
вызывает __add__
в случае AttributeError
?
- person mgilson; 13.03.2013
i.__iadd__
не звонит __add__
; это i += 1
звонит __add__
.
- person abarnert; 13.03.2013
i = i.__iadd__(1)
- iadd
может изменить объект на месте, но не обязана, поэтому ожидается, что результат будет возвращен в любом случае.
- person lvc; 13.03.2013
operator.iadd
вызывает __add__
для AttributeError
, но не может повторно связать результат... поэтому i=1; operator.iadd(i, 1)
возвращает 2, а i
оставляет равным 1
. Что немного сбивает с толку.
- person abarnert; 13.03.2013
self
. Позвольте мне посмотреть, как прояснить это в ответе.
- person abarnert; 13.03.2013
+=
всегда перепривязывает переменную, а operator.iadd
— нет. Вот почему в документах прямо говорится, что a = iadd(a, b)
эквивалентно a += b
, а не iadd(a, b)
эквивалентно a += b
.
- person abarnert; 13.03.2013
Вот пример, который напрямую сравнивает 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]
+=
действует какextend()
в случае списков. - person Ashwini Chaudhary   schedule 13.03.2013i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]
этоTrue
. Многие разработчики могут не заметить, чтоid(i)
меняется для одной операции, но не для другой. - person kojiro   schedule 13.03.2013