为什么在似乎是数据副本的操作上修改原始数据?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么在似乎是数据副本的操作上修改原始数据?相关的知识,希望对你有一定的参考价值。

让我们引用numpy手册:https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#advanced-indexing

当选择对象obj是非元组序列对象,ndarray(数据类型为integer或bool)或具有至少一个序列对象或ndarray(数据类型为integer或bool)的元组时,将触发高级索引。高级索引有两种类型:整数和布尔值。

高级索引始终返回数据的副本(与返回视图的基本切片形成对比)。

然后对高级索引返回的内容进行操作永远不应该修改原始数组。事实上:

import numpy as np

arr = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90])
indexes = np.array([3, 6, 4])

slicedArr = arr[indexes]
slicedArr *= 5
arr

这打印:

array([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90])

但是,情况似乎并非总是如此。奇怪的是,如果我不保存[]运算符返回的任何中间变量,我会以某种方式在原始数组上运行。请考虑这个例子:

import numpy as np

arr = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90])
indexes = np.array([3, 6, 4])

arr[indexes] *= 5
arr

这打印:

array([  0,  10,  20, 150, 200,  50, 300,  70,  80,  90])

我不抱怨。实际上,这对我来说是一个救生员。然而,我不明白为什么这样做,我真的很想理解这一点。

据我所知,只要我写arr[indexes],我就会创建一个数组的副本;所以随后的*= 5应该在这个副本上运行而不是在原始阵列上运行。但是,应该放弃这种计算的结果,因为它不会写入任何变量。

但显然我错了。

我的误会在哪里?

答案

虽然声明

a = expr

a[x] = expr

看起来相似,它们实际上是根本不同的。第一个将名称'a'绑定到expr。第二个是或多或少equivalenta.__setitem__(x, expr)__setitem__实际上做的是取决于实现它的人,但传统的语义是用ax指示的位置更新容器对象expr。特别是,没有创建“代表a[x]”的中间对象。

只是为了完整性a[x],如果它不在l.h.s.s.在语法上看起来像一个赋值或多或少等同于a.__getitem__(x)

更新以回应后续问题(执行a[x] *= 5时会发生什么?)让我们检测相关方法,以便我们可以看到发生了什么。下面,__imul__是就地乘法“魔法”:

import numpy as np

class spy(np.ndarray):
    def __getitem__(self, key):
        print('getitem',  key)
        return super().__getitem__(key)
    def __setitem__(self, key, value):
        print('setitem', key)
        return super().__setitem__(key, value)
    def __imul__(self, other):
        print('imul', other)
        return super().__imul__(other)

a = spy((5, 5))
a[...] = 1
a[[1,2],[4,2]] *= 5

打印:

setitem Ellipsis
getitem ([1, 2], [4, 2])
imul 5
setitem ([1, 2], [4, 2])
另一答案

这里的关键点是

高级索引始终返回数据的副本

在第二个示例中,您没有使用索引返回任何内容。您只使用索引来修改值。因此,您正在修改的对象是原始对象。不是副本。

以上是关于为什么在似乎是数据副本的操作上修改原始数据?的主要内容,如果未能解决你的问题,请参考以下文章

JAVA中写时复制(Copy-On-Write)Map实现

如何在修改其副本时避免更新原始数组? [复制]

复制一片指针(指向新值)

MySQL备份与还原

数据库 之 数据备份和恢复概念

为什么分布式架构一定要考虑容错?