为什么在似乎是数据副本的操作上修改原始数据?
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。第二个是或多或少equivalent到a.__setitem__(x, expr)
。 __setitem__
实际上做的是取决于实现它的人,但传统的语义是用a
在x
指示的位置更新容器对象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])
这里的关键点是
高级索引始终返回数据的副本
在第二个示例中,您没有使用索引返回任何内容。您只使用索引来修改值。因此,您正在修改的对象是原始对象。不是副本。
以上是关于为什么在似乎是数据副本的操作上修改原始数据?的主要内容,如果未能解决你的问题,请参考以下文章