Python 中 a-= b 和 a = a-b 的区别

我最近应用 这个解决平均每 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数组就不行呢?

8063 次浏览

Internally, the difference is that this:

a[1:] -= a[:-1]

is equivalent to this:

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)))

while this:

b[1:] = b[1:] - b[:-1]

maps to this:

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)))

In some cases, __sub__() and __isub__() work in a similar way. But mutable objects should mutate and return themselves when using __isub__(), while they should return a new object with __sub__().

Applying slice operations on numpy objects creates views on them, so using them directly accesses the memory of the "original" object.

Note: using in-place operations on NumPy arrays that share memory in no longer a problem in version 1.13.0 onward (see details here). The two operation will produce the same result. This answer only applies to earlier versions of NumPy.


Mutating arrays while they're being used in computations can lead to unexpected results!

In the example in the question, subtraction with -= modifies the second element of a and then immediately uses that modified second element in the operation on the third element of a.

Here is what happens with a[1:] -= a[:-1] step by step:

  • a is the array with the data [1, 2, 3].

  • We have two views onto this data: a[1:] is [2, 3], and a[:-1] is [1, 2].

  • The in-place subtraction -= begins. The first element of a[:-1], 1, is subtracted from the first element of a[1:]. This has modified a to be [1, 1, 3]. Now we have that a[1:] is a view of the data [1, 3], and a[:-1] is a view of the data [1, 1] (the second element of array a has been changed).

  • a[:-1] is now [1, 1] and NumPy must now subtract its second element which is 1 (not 2 anymore!) from the second element of a[1:]. This makes a[1:] a view of the values [1, 2].

  • a is now an array with the values [1, 1, 2].

b[1:] = b[1:] - b[:-1] does not have this problem because b[1:] - b[:-1] creates a new array first and then assigns the values in this array to b[1:]. It does not modify b itself during the subtraction, so the views b[1:] and b[:-1] do not change.


The general advice is to avoid modifying one view inplace with another if they overlap. This includes the operators -=, *=, etc. and using the out parameter in universal functions (like np.subtract and np.multiply) to write back to one of the arrays.

The docs say :

The idea behind augmented assignment in Python is that it isn't just an easier way to write the common practice of storing the result of a binary operation in its left-hand operand, but also a way for the left-hand operand in question to know that it should operate `on itself', rather than creating a modified copy of itself.

As a thumb rule, augmented substraction (x-=y) is x.__isub__(y), for IN-place operation IF possible, when normal substraction (x = x-y) is x=x.__sub__(y) . On non mutable objects like integers it's equivalent. But for mutable ones like arrays or lists, as in your example, they can be very different things.