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

gaa*_*kam 9 python arrays numpy copy

让我们引用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
Run Code Online (Sandbox Code Playgroud)

这打印:

array([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90])
Run Code Online (Sandbox Code Playgroud)

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

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

这打印:

array([  0,  10,  20, 150, 200,  50, 300,  70,  80,  90])
Run Code Online (Sandbox Code Playgroud)

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

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

但显然我错了.

我的误会在哪里?

Pau*_*zer 6

虽然声明

a = expr
Run Code Online (Sandbox Code Playgroud)

a[x] = expr
Run Code Online (Sandbox Code Playgroud)

看起来相似,它们实际上是根本不同的.第一个将名称'a'绑定到expr.第二个是或多或少等效a.__setitem__(x, expr).什么__setitem__实际上做的是最多谁实施,但传统的语义是更新容器对象a在所指示的位置xexpr.特别是,没有a[x]创建"代表"的中间对象.

只是为了完整性,a[x]如果它不是语法上看起来像一个赋值或多或少相当的lhs 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
Run Code Online (Sandbox Code Playgroud)

打印:

setitem Ellipsis
getitem ([1, 2], [4, 2])
imul 5
setitem ([1, 2], [4, 2])
Run Code Online (Sandbox Code Playgroud)