【问题标题】:Difference between a -= b and a = a - b in PythonPython中a -= b和a = a - b之间的区别
【发布时间】:2016-05-04 07:57:30
【问题描述】:

我最近应用了this 解决方案来平均每 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 数组?

【问题讨论】:

    标签: python arrays numpy variable-assignment in-place


    【解决方案1】:

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


    在计算中使用数组变异可能会导致意想不到的结果!

    在问题的示例中,-= 的减法修改了a 的第二个元素,然后在a 的第三个元素的运算中立即使用修改的第二个元素。

    以下是a[1:] -= a[:-1] 的操作步骤:

    • a 是包含数据[1, 2, 3] 的数组。

    • 我们对此数据有两个视图:a[1:][2, 3]a[:-1][1, 2]

    • 就地减法-= 开始。从a[1:] 的第一个元素中减去a[:-1] 的第一个元素1。这已将 a 修改为 [1, 1, 3]。现在我们有a[1:] 是数据[1, 3] 的视图,a[:-1] 是数据[1, 1] 的视图(数组a 的第二个元素已更改)。

      李>
    • a[:-1] 现在是 [1, 1],NumPy 现在必须从 a[1:] 的第二个元素中减去它的第二个元素 ,即 1(不再是 2!)。这使得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 参数写回数组之一。

    【讨论】:

    • 比起目前接受的答案,我更喜欢这个答案。它使用非常清晰的语言来展示修改可变对象的效果。更重要的是,最后一段直接强调了对重叠视图进行就地修改的重要性,这应该是从这个问题中吸取的教训。
    【解决方案2】:

    在内部,区别在于:

    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 对象应用切片操作会在它们上创建视图,因此使用它们会直接访问“原始”对象的内存。

    【讨论】:

      【解决方案3】:

      The docs 说:

      Python 中的增强赋值背后的想法是它不是 只是一种更简单的方法来编写存储 其左侧操作数中的二元运算的结果,但也是 有问题的左操作数知道它应该 对自身进行操作,而不是创建一个修改过的副本 自己。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-06-26
        • 1970-01-01
        • 2014-12-05
        • 1970-01-01
        • 2017-12-24
        • 1970-01-01
        • 2019-12-18
        相关资源
        最近更新 更多