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

ias*_*nas 88 python arrays numpy in-place variable-assignment

我最近应用这个解决方案来平均每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
Run Code Online (Sandbox Code Playgroud)

哪个输出:

[1 1 2]
[1 1 1]
Run Code Online (Sandbox Code Playgroud)

因此,在数组的情况下a -= b产生不同的结果a = a - b.直到现在我才想到这两种方式完全相同.有什么不同?

为什么我提到的用于求和矩阵中每N行的方法是如何工作的,例如7x4矩阵而不是7x1阵列?

Ale*_*ley 79

注意:在版本1.13.0以后的共享内存的NumPy阵列上使用就地操作不再是问题(请参阅此处的详细信息).这两个操作将产生相同的结果.此答案仅适用于早期版本的NumPy.


在计算中使用变异数组会导致意外结果!

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

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

  • a是带有数据的数组[1, 2, 3].

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

  • 就地减法-=开始.从1的第一个元素中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.subtractnp.multiply),以写回阵列中的一个.

  • 我更喜欢这个答案,而不是目前接受的答案.它使用非常清晰的语言来显示修改可变对象的效果.更重要的是,最后一段直接强调了对重叠观点进行就地修改的重要性,这应该是从这个问题中回归的教训. (4认同)

glg*_*lgl 43

在内部,区别在于:

a[1:] -= a[:-1]
Run Code Online (Sandbox Code Playgroud)

相当于:

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)))
Run Code Online (Sandbox Code Playgroud)

而这个:

b[1:] = b[1:] - b[:-1]
Run Code Online (Sandbox Code Playgroud)

映射到这个:

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)))
Run Code Online (Sandbox Code Playgroud)

在某些情况下,__sub__()__isub__()以类似的方式工作.但是可变对象在使用时应该变异并返回自己__isub__(),而它们应该返回一个新对象__sub__().

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


B. *_* M. 11

文档说:

在Python中扩充赋值背后的想法是,它不仅是一种更简单的方法来编写将二进制操作的结果存储在其左侧操作数中的常见实践,而且还有一种方法可以解决左侧操作数的问题.知道它应该"自己"运行,而不是创建自己的修改副本.

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