理解引用:为什么这个 Numpy 赋值不起作用?

问题描述

我有一些像这样的测试代码

import numpy as np

foo = np.zeros(1,dtype=int)
bar = np.zeros((10,1),dtype=int)


foo_copy = np.copy(foo)
bar[-1] = foo_copy

foo_copy[-1] = 10

print(foo_copy)
print(bar)

我期望 foo_copybar 的最后一个元素都包含值 10,但 bar 的最后一个元素仍然是一个包含值 0 的 np 数组。

[10]
[[0]
 [0]
 [0]
 [0]
 [0]
 [0]
 [0]
 [0]
 [0]
 [0]]  # <<--- why not 10?

最后一个元素不是指向 foo_copy 吗?

或者在所有作业中,np 都会复制数据而我无法使用原始 ndarray 更改它?

如果是这样,有没有办法将最后一个元素保留为指向 foo_bar 的指针?

解决方法

numpy 数组具有数值,而不是引用(至少对于数值 dtypes):

制作一维数组,并将其重塑为二维:

In [64]: bar = np.arange(12).reshape(4,3)
In [65]: bar
Out[65]: 
array([[ 0,1,2],[ 3,4,5],[ 6,7,8],[ 9,10,11]])

另一个一维数组:

In [66]: foo = np.array([10])
In [67]: foo
Out[67]: array([10])

此分配是按值分配的:

In [68]: bar[1,1] = foo
In [69]: bar
Out[69]: 
array([[ 0,11]])

也是这样,虽然值是 broadcasted 到整行:

In [70]: bar[2] = foo
In [71]: bar
Out[71]: 
array([[ 0,[10,10],11]])

我们可以view将二维数组作为一维数组。这是值实际存储方式的更接近表示(但在 c 字节数组中,12*8 字节长):

In [72]: bar1 = bar.ravel()
In [73]: bar1
Out[73]: array([ 0,2,3,5,9,11])

改变view的一个元素会改变2d的对应元素:

In [74]: bar1[3] = 30
In [75]: bar
Out[75]: 
array([[ 0,[30,11]])

虽然我们可以制作 object dtype 数组,它像列表一样存储引用,但它们没有任何性能优势。

包含 bar 的“原始数据”的字节串:

In [76]: bar.tobytes()
Out[76]: b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'

传说中的 numpy 速度来自于使用编译后的 c 代码处理这些原始数据。使用 Python 代码访问单个元素相对较慢。像 bar*3 这样的全数组操作速度很快。