Мне сказали, что +=
может иметь разные эффекты, чем стандартная нотация i = i +
. Есть ли случай, когда i += 1
будет отличаться от i = i + 1
?Когда «i + = x» отличается от «i = i + x» в Python?
ответ
Это полностью зависит от объекта 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
, он фактически меняет b
(и a
видит, что тоже меняется). В конце концов, он ссылается на тот же список). Во втором случае, однако, когда я делаю 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
.
Ну, '+ =' вызывает '__iadd__' _if, что он существует_, и возвращается обратно к добавлению и перезаписи в противном случае. Вот почему 'i = 1; i + = 1' работает, хотя нет 'int .__ iadd__'. Но кроме этого незначительного, прекрасные объяснения. – abarnert
@abarnert - Я всегда предполагал, что 'int .__ iadd__' просто называется' __add__'. Я рад, что сегодня узнал что-то новое :). – mgilson
@abarnert - Я полагаю, возможно, что он * полный *, 'x + y' вызывает' y .__ radd __ (x) ', если' x .__ add__' не существует (или возвращает 'NotImplemented' и' x' и ' y' имеют разные типы) – mgilson
Под одеялом, 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'? – mgilson
Ну, 'i .__ iadd__' не называет' __add__'; это 'i + = 1', который вызывает' __add__'. – abarnert
errr ... Да, вот что я имел в виду. Интересно. Я не понимал, что это было сделано автоматически. – mgilson
Если вы просто имеете дело с литералами, то i += 1
имеет то же поведение, что и i = i + 1
.
Чувствуете ли вы, что существующие ответы не уточняют этот факт? Это кажется ненужным ответом, учитывая обширный ответ, данный mgilson. – Guvante
Да, потому что ни один из существующих ответов просто не касался фактического вопроса, они шли во всевозможные направления, в то время как я интерпретировал этот вопрос как более простой, включающий литералы. –
В Python термин «литерал» имеет два разных значения, возможно, ни один из них не является тем, о чем вы думаете. Существуют литеральные жетоны (которые не включают в себя 'None',' -1' или '' a '' b''), или литералы (включая отображения list/tuple/dict). На уровне литералов структуры 'i + = [1]' не имеют такого же поведения, как 'i = i + [1]', хотя он имеет дело с листингом отображения списка. – abarnert
Вот пример, который непосредственно сравнивает 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]
Говоря простыми словами, у вас есть два случая:
i = i + 1
Это создает новую переменную я, используя значение предыдущего I и увеличивает его на единицу и сохраняет его в другом месте памяти.
i += 1
Это не создает новую переменную, а увеличивает приращение переменной i в той же ячейке памяти. Это намного эффективнее предыдущего.
- 1. Симпимовая подстановка x [i] * x [j] с x [i, j]
- 2. Как сделать «i + = x» работает как «i ++»?
- 3. Почему «for (i = 0; i == x; i ++)» возвращает undefined?
- 4. Matlab x = [x [i]]; означает?
- 5. Проблема с массивом (если (zeds [i] .x == zeds [i] .x))
- 6. заменяя x [i на x [i] в R для i = 1,2,3,4
- 7. Выполнение printf для printf x [i] где значение actall i и просто не «x [i]»
- 8. Компиляция выражения ++ i + ++ i + ++ i?
- 9. Пролог дедушка (i, i)
- 10. Оценка приращения прихода i + i ++ + i + i ++;
- 11. Python 3 - float (X) * i = int (Z)
- 12. Когда использовать 'i' vs. 'str [i]'?
- 13. Самый короткий PHP-код для $ i = $ i + $ x;
- 14. javascript можно использовать многоугольник [i] .x вместо полигона [i] [j]?
- 15. Понимание; для i в диапазоне, x, y = [int (i) в i .... Python3
- 16. В чем разница между (x в массиве) и (i = 0; i <array.length; i ++)
- 17. Основы jQuery (теория) - что я могу сделать с параметром index (i) в .text (i, x), .html (i, x) или .val (i, x)?
- 18. Альтернатива 'для i в xrange (len (x))'
- 19. Что такое эквивалент x [i:] в Scala?
- 20. i ++, i = i + 1 и i + = 1, который быстрее?
- 21. для (i = 0; True; i ++) в python?
- 22. Почему i ++++++++ i действителен в python?
- 23. Приращение ++ i, i ++ и i + = 1
- 24. Поиск указателя i, где A [i] = i
- 25. Почему Math.sqrt (i * i) .floor == i?
- 26. Почему ++ i эффективнее i ++?
- 27. Поиск в отсортированном массиве X для первого индекса i, так что X [i]> = a
- 28. Оптимизация «i = b? (I | mask): (i & ~ mask)«
- 29. Как работает следующее выражение i = + i * i ;?
- 30. Что оценит i ++ + i ++ в C++ 17?
'+ =' действует как 'extend()' в случае списков. –
@AshwiniChaudhary Это довольно тонкое различие, учитывая, что 'i = [1,2,3]; i = i + [4,5,6]; i == [1,2,3,4,5,6]' «Истина». Многие разработчики могут не заметить, что 'id (i)' изменяется для одной операции, но не другой. – kojiro
@kojiro - Хотя это тонкое различие, я думаю, что это важно. – mgilson