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

Difference between a -= b and a = a - b in 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 阵列?

在内部,区别在于:

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__() 时改变 return 自身,而它们应该 return 一个带有 __sub__().

的新对象

对 numpy 对象应用切片操作会在它们上创建视图,因此使用它们会直接访问 "original" 对象的内存。

注意:在 NumPy 数组上使用 in-place 操作共享内存在 1.13.0 及更高版本中不再是问题(详见 here)。这两个操作将产生相同的结果。此答案仅适用于早期版本的 NumPy。


在计算中使用数组时改变数组可能会导致意外结果!

在问题的例子中,-= 的减法修改了 a 的第二个元素,然后立即在操作中使用 修改的 第二个元素在 a.

的第三个元素上

下面是 a[1:] -= a[:-1] 一步一步发生的事情:

  • a 是包含数据 [1, 2, 3].

  • 的数组
  • 我们对这个数据有两种看法:a[1:][2, 3]a[:-1][1, 2]

  • in-place减法-=开始。 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]先创建了一个new数组,然后把这个数组中的值赋值给b[1:] .它在减法期间不会修改 b 本身,因此视图 b[1:]b[:-1] 不会更改。


一般建议是避免在一个视图与另一个视图重叠时就地修改它们。这包括运算符 -=*= 等,并在通用函数(如 np.subtractnp.multiply)中使用 out 参数写回其中一个数组。

The docs 说:

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.

作为经验法则,增减法(x-=y)是x.__isub__(y),对于IN-place操作IF 可能,当正常减法 (x = x-y) 为 x=x.__sub__(y) 时。在像整数这样的非可变对象上,它是等价的。但是对于像数组或列表这样的可变对象,就像你的例子一样,它们可能是非常不同的东西。