Примечание: использование операций на месте с массивами 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:]
есть представление данных [1, 3]
и a[:-1]
представление данных [1, 1]
(второй элемент массива a
был изменен).
a[:-1]
теперь, [1, 1]
и NumPy теперь должен вычесть свой второй элемент, который равен 1 (а не 2!), из второго элемента a[1:]
. Это дает a[1:]
представление о ценностях [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-объектам создает на них представления, поэтому их использование напрямую обращается к памяти «исходного» объекта.
источник
В документах говорится:
Как правило, расширенное вычитание (
x-=y
)x.__isub__(y)
для операции IN -place, ЕСЛИ возможно, когда есть обычное вычитание (x = x-y
)x=x.__sub__(y)
. Для неизменяемых объектов, таких как целые числа, это эквивалентно. Но для изменяемых, таких как массивы или списки, как в вашем примере, они могут быть очень разными.источник