2013-03-13 15 views
179

Мне сказали, что += может иметь разные эффекты, чем стандартная нотация i = i +. Есть ли случай, когда i += 1 будет отличаться от i = i + 1?Когда «i + = x» отличается от «i = i + x» в Python?

+6

'+ =' действует как 'extend()' в случае списков. –

+11

@AshwiniChaudhary Это довольно тонкое различие, учитывая, что 'i = [1,2,3]; i = i + [4,5,6]; i == [1,2,3,4,5,6]' «Истина». Многие разработчики могут не заметить, что 'id (i)' изменяется для одной операции, но не другой. – kojiro

+1

@kojiro - Хотя это тонкое различие, я думаю, что это важно. – mgilson

ответ

280

Это полностью зависит от объекта i.

+= называет __iadd__ method (если она существует - падение назад на __add__, если он не существует), тогда как + называет __add__ method или __radd__ method in a few cases .

С точки зрения 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.


В выражении 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)

В выражении foo_instance + bar_instance, bar_instance.__radd__ будет проверяться до foo_instance.__add__, если тип bar_instance является подклассом типа foo_instance (например, issubclass(Bar, Foo)). Рациональным для этого является то, что Bar в некотором смысле является объектом «более высокого уровня», чем Foo, поэтому Bar должен получить возможность переопределения поведения Foo.

+15

Ну, '+ =' вызывает '__iadd__' _if, что он существует_, и возвращается обратно к добавлению и перезаписи в противном случае. Вот почему 'i = 1; i + = 1' работает, хотя нет 'int .__ iadd__'. Но кроме этого незначительного, прекрасные объяснения. – abarnert

+3

@abarnert - Я всегда предполагал, что 'int .__ iadd__' просто называется' __add__'. Я рад, что сегодня узнал что-то новое :). – mgilson

+0

@abarnert - Я полагаю, возможно, что он * полный *, 'x + y' вызывает' y .__ radd __ (x) ', если' x .__ add__' не существует (или возвращает 'NotImplemented' и' x' и ' y' имеют разные типы) – mgilson

63

Под одеялом, 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 он был уже связан с, так что вы можете обычно игнорировать эту часть.)

+0

действительно '__iadd__' на самом деле вызывает' __add__' в случае 'AttributeError'? – mgilson

+0

Ну, 'i .__ iadd__' не называет' __add__'; это 'i + = 1', который вызывает' __add__'. – abarnert

+0

errr ... Да, вот что я имел в виду. Интересно. Я не понимал, что это было сделано автоматически. – mgilson

2

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

+19

Чувствуете ли вы, что существующие ответы не уточняют этот факт? Это кажется ненужным ответом, учитывая обширный ответ, данный mgilson. – Guvante

+5

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

+2

В Python термин «литерал» имеет два разных значения, возможно, ни один из них не является тем, о чем вы думаете. Существуют литеральные жетоны (которые не включают в себя 'None',' -1' или '' a '' b''), или литералы (включая отображения list/tuple/dict). На уровне литералов структуры 'i + = [1]' не имеют такого же поведения, как 'i = i + [1]', хотя он имеет дело с листингом отображения списка. – abarnert

5

Вот пример, который непосредственно сравнивает 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] 
-2

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

i = i + 1 

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

i += 1 

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

Смежные вопросы