Разница между a - = b и a = a - b в Python
Я недавно подал заявку этой решение для усреднения всех N строк матрицы.
Хотя решение работает в целом, у меня были проблемы при применении к массиву 7x1. Я заметил, что проблема заключается в использовании -= оператора.
Чтобы сделать небольшой пример:
import numpy as np
a = np.array([1,2,3])
b = np.copy(a)
a[1:] -= a[:-1]
b[1:] = b[1:] - b[:-1]
print a
print b
выходы:
[1 1 2]
[1 1 1]
Итак, в случае массива a -= b дает другой результат, чем a = a - b. До сих пор я думал, что эти два пути совершенно одинаковы. Что? разница?
почему метод, который я упоминаю для суммирования всех N строк в матрице, работает, например, для матрицы 7x4, но не для массива 7x1?
3 ответов
Примечание: использование операций на месте на массивах NumPy, которые разделяют память, больше не является проблемой в версии 1.13.0 (см. Подробнее здесь). Две операции приведут к одному и тому же результату. Этот ответ применим только к более ранним версиям NumPy.
мутация массивов во время их использования в вычислениях может привести к неожиданным результатам!
в Примере в вопросе, вычитание с -= изменяет второй элемент a а затем сразу же использует это изменен второй элемент в операции над третьим элементом a.
вот что происходит с a[1:] -= a[:-1] шаг за шагом:
aмассив с данными[1, 2, 3].у нас есть два представления об этих данных:
a[1:]is[2, 3]иa[:-1]и[1, 2].вычитание на месте
-=начинается. Этот первый элементa[:-1], 1, вычитается из первого элементаa[1:]. Это изменилоaна[1, 1, 3]. Теперь у нас есть этоa[1:]ввиду иa[:-1]ввиду (второй элемент массиваaбыла изменена).a[:-1]теперь[1, 1]и NumPy теперь должен вычесть свой второй элемент что 1 (не 2 больше!) из второго элементаa[1:]. Это делаетa[1:]a просмотр значений[1, 2].aтеперь является массивом со значениями[1, 1, 2].
b[1:] = b[1:] - b[:-1] не имеет этой проблемы, потому что b[1:] - b[:-1] создает новая сначала массив, а затем присваивает значения в этом массиве b[1:]. Он не изменяет b сам во время вычитания, поэтому представления b[1:] и b[:-1] не меняются.
общий совет-избегать изменения одного вид inplace с другим, если они перекрываются. Это включает в себя операторы -=, *=, etc. и используя out параметр в универсальных функциях (например,np.subtract и np.multiply) для записи в один из массивов.
внутренне, разница в том, что это:
a[1:] -= a[:-1]
эквивалентно этому:
a[1:] = a[1:].__isub__(a[:-1])
a.__setitem__(slice(1, None, None), a.__getitem__(slice(1, None, None)).__isub__(a.__getitem__(slice(1, None, None)))
а это:
b[1:] = b[1:] - b[:-1]
карты для этого:
b[1:] = b[1:].__sub__(b[:-1])
b.__setitem__(slice(1, None, None), b.__getitem__(slice(1, None, None)).__sub__(b.__getitem__(slice(1, None, None)))
в некоторых случаях __sub__() и __isub__() работа аналогичным образом. Но изменяемые объекты должны мутировать и возвращаться при использовании __isub__(), в то время как они должны возвращать новый объект с __sub__().
применение операций среза к объектам numpy создает представления на них, поэтому их использование непосредственно обращается к памяти "исходного" объекта.
документы говорят :
идея расширенного назначения в Python заключается в том, что это не просто более простой способ написать общую практику хранения результат двоичной операции в его левом операнде, но также и способ для левого операнда, о котором идет речь, знать, что он должен работайте `на себе", а не создавайте модифицированную копию себя.
как правило большого пальца, дополненная подстрока (x-=y) is x.__isub__(y), for наместо работы если возможно, когда нормальная подстрока (x = x-y) is x=x.__sub__(y) . На изменяемые объекты, как числа эквиваленте. Но для изменяемых, таких как массивы или списки, как в вашем примере, они могут быть очень разными.