Разница между 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 88

3 ответа:

Примечание: использование операций на месте для массивов NumPy, которые совместно используют память, больше не является проблемой в версии 1.13.0 (см. подробности здесь). Две операции приведут к одному и тому же результату. Этот ответ применим только к более ранним версиям NumPy.


мутация массивов во время их использования в вычислениях может привести к неожиданным результатам!

в Примере в вопросе, вычитание с -= изменяет второй элемент a а потом сразу же использует это изменен второй элемент в операции над третьим элементом a.

вот что происходит с a[1:] -= a[:-1] шаг за шагом:

  • a массив с данными [1, 2, 3].

  • у нас есть два взгляда на эти данные: a[1:] и [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] не меняются.


общий совет, чтобы избежать изменения одного вид на месте с другим, если они перекрываются. Это включает в себя операторы -=,*= и т. д. и с помощью 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 заключается в том, что это не так просто более простой способ написать общую практику хранения результат двоичной операции в ее левом операнде, но также и A способ для рассматриваемого левого операнда знать, что он должен работать `на себя', а не создавать модифицированные копии себя.

как правило большого пальца, расширенное вычитание (x-=y) составляет x.__isub__(y) на наместо работы если возможно, когда нормальное вычитание (x = x-y) составляет x=x.__sub__(y) . На изменяемые объекты, как числа эквиваленте. Но для изменчивых, таких как массивы или списки, как в вашем примере, они могут быть очень разными вещами.