2016-01-27 17 views
88

Я недавно применил решение this для усреднения каждых N строк матрицы. Хотя решение работает в целом, у меня были проблемы при применении к массиву 7x1. Я заметил, что проблема заключается в использовании оператора -=. Для того, чтобы небольшой пример:Разница между a - = b и a = a - b в Python

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?

ответ

79

Примечание: использование операций на месте в массивах NumPy, которые совместно используют память, больше не является проблемой в версии 1.13.0 (см. Подробности here). Эти две операции приведут к такому же результату. Этот ответ применим только к более ранним версиям 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) для записи обратно в один из массивов.

+4

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

42

Внутренне, разница в том, что это:

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 создает на них виды, поэтому их использование напрямую обращается к памяти «оригинального» объекта.

11

The docs говорят:

Идея дополненной назначения в Python, что это не просто простой способ написать общую практику хранения результат двоичной операции в ее левой руке операнд, но также и путь для левого операнда, который должен знать, что он должен использовать `сам по себе ', а не создавать измененную копию .

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

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