@AshwiniChaudhary Это довольно тонкое различие, учитывая, что i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]это так True. Многие разработчики могут не заметить, что id(i)изменения для одной операции, но не для другой.
Кодзиро
1
@kojiro - Хотя это тонкое различие, я думаю, что оно важное.
Мгилсон
@mgilson это важно, и поэтому я чувствовал, что нужно объяснение. :)
С точки зрения 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__то результат здесь такой же, как
2 В выражении foo_instance + bar_instance, bar_instance.__radd__будет судим , foo_instance.__add__если тип bar_instanceявляется подклассом типа foo_instance(например issubclass(Bar, Foo)). Рационально это потому , что Barв каком - то смысле «более высокого уровня» объекта , чем Fooтак Barдолжны получить возможность переопределения Fooповедения «s.
Ну, +=вызывает, __iadd__если он существует , и в противном случае возвращается к добавлению и повторному связыванию. Вот почему i = 1; i += 1работает, хотя нет int.__iadd__. Но кроме этой незначительной гниды, отличные объяснения.
abarnert
4
@abarnert - я всегда предполагал, что int.__iadd__только что звонил __add__. Я рад, что узнал что-то новое сегодня :).
Мгилсон
@abarnert - я полагаю , может быть , чтобы быть полным , x + yвызовы , y.__radd__(x)если x.__add__не существует (или возврат NotImplementedи xи yимеют различные типы)
mgilson
Если вы действительно хотите быть законченным, вам следует упомянуть, что бит «если он существует» проходит через обычные механизмы getattr, за исключением некоторых причуд с классическими классами, а для типов, реализованных в C API, вместо этого он ищет либо nb_inplace_addили sq_inplace_concat, и те функции C API имеют более строгие требования, чем методы Python, и ... Но я не думаю, что это имеет отношение к ответу. Основное различие заключается в том, что он +=пытается сделать добавление на месте, прежде чем вернуться к действию подобного +, что, я думаю, вы уже объяснили.
abarnert
Да, я полагаю, вы правы ... Хотя я мог бы просто отступить от позиции, что C API не является частью Python . Это часть Cpython :-P
mgilson
67
Под прикрытием i += 1делает что-то вроде этого:
try:
i = i.__iadd__(1)exceptAttributeError:
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?
Мгилсон
Ну, i.__iadd__не звонит __add__; это i += 1те звонки __add__.
abarnert
ээээ ... да, это то, что я имел в виду Интересный. Я не осознавал, что это было сделано автоматически.
Мгилсон
3
Первая попытка на самом деле i = i.__iadd__(1)- iaddможет изменить объект на месте, но не обязан, и поэтому ожидается, что в любом случае вернет результат.
13:30
Обратите внимание , что это означает , что operator.iaddвызовы __add__на AttributeError, но он не может Пересвяжите результат ... так i=1; operator.iadd(i, 1)возвращается 2 и листьев iустановлен 1. Что немного сбивает с толку.
abarnert
6
Вот пример, который напрямую сравнивается 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()
в случае списков.i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]
это такTrue
. Многие разработчики могут не заметить, чтоid(i)
изменения для одной операции, но не для другой.Ответы:
Это полностью зависит от объекта
i
.+=
вызывает__iadd__
метод (если он существует - возвращается к нему,__add__
если он не существует), тогда как в некоторых случаях+
вызывает__add__
метод 1 или__radd__
метод 2 .С точки зрения API,
__iadd__
предполагается использовать для изменения изменяемых объектов на месте (возвращая объект, который был мутирован), тогда как__add__
должен возвращать новый экземпляр чего-либо. Для неизменяемых объектов оба метода возвращают новый экземпляр, но__iadd__
помещают новый экземпляр в текущее пространство имен с тем же именем, что и у старого экземпляра. Вот почемупохоже на прирост
i
. В действительности вы получаете новое целое число и назначаете его «поверх»,i
теряя одну ссылку на старое целое число. В этом случаеi += 1
точно так же, какi = i + 1
. Но с большинством изменяемых объектов это другая история:В качестве конкретного примера:
по сравнению с:
обратите внимание на то, как в первом примере,
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
поведения «s.источник
+=
вызывает,__iadd__
если он существует , и в противном случае возвращается к добавлению и повторному связыванию. Вот почемуi = 1; i += 1
работает, хотя нетint.__iadd__
. Но кроме этой незначительной гниды, отличные объяснения.int.__iadd__
только что звонил__add__
. Я рад, что узнал что-то новое сегодня :).x + y
вызовы ,y.__radd__(x)
еслиx.__add__
не существует (или возвратNotImplemented
иx
иy
имеют различные типы)nb_inplace_add
илиsq_inplace_concat
, и те функции C API имеют более строгие требования, чем методы Python, и ... Но я не думаю, что это имеет отношение к ответу. Основное различие заключается в том, что он+=
пытается сделать добавление на месте, прежде чем вернуться к действию подобного+
, что, я думаю, вы уже объяснили.Под прикрытием
i += 1
делает что-то вроде этого:Пока
i = i + 1
делает что-то вроде этого:Это небольшое упрощение, но вы понимаете: Python предоставляет типам способ обработки
+=
, создавая как__iadd__
метод, так и метод__add__
.Предполагается, что изменяемые типы, например
list
, будут мутировать сами__iadd__
(и затем возвращатьсяself
, если вы не делаете что-то очень сложное), в то время как неизменяемые типы, напримерint
, просто не будут реализовывать это.Например:
Потому что
l2
это тот же объектl1
, что и вы мутировалиl1
, вы также мутировалиl2
.Но:
Здесь вы не мутировали
l1
; вместо этого вы создали новый списокl1 + [3]
и отскочили от имени,l1
чтобы указать на него, оставивl2
указатель на исходный список.(В
+=
версии вы также связывалиl1
, просто в этом случае вы связывали его с тем же, с которымlist
он уже был связан, так что вы обычно можете игнорировать эту часть.)источник
__iadd__
самом деле звонить__add__
в случаеAttributeError
?i.__iadd__
не звонит__add__
; этоi += 1
те звонки__add__
.i = i.__iadd__(1)
-iadd
может изменить объект на месте, но не обязан, и поэтому ожидается, что в любом случае вернет результат.operator.iadd
вызовы__add__
наAttributeError
, но он не может Пересвяжите результат ... такi=1; operator.iadd(i, 1)
возвращается 2 и листьевi
установлен1
. Что немного сбивает с толку.Вот пример, который напрямую сравнивается
i += x
сi = i + x
:источник