numpy 的基本数据类型是ndarray(数组),ndarray在内存中的是一维连续存储,支持索引。换句话说,数组映射一个连续的内存块,可以使用索引值进行数据访问。索引依次由形状和数据类型定义。
# 创建一个0-9的数组,改为3行3列的二维数组,单个元素的数据类型为np.int16(2个字节)
z = np.arange(9).reshape(3,3).astype(np.int16)
print(z.itemsize) # 单个元素的字节数
2
print(z.shape) # 数组的形状
(3, 3)
print(Z.ndim) # 数组的维数
2
可以算出数组的stides步长,步长定义了遍历数组时每个维度中要跨步的字节数。
strides = z.shape[1]*z.itemsize, z.itemsize
print(strides)
(6, 2)
print(z.strides)
# 第一个维度,也就是行方向上每行要跨出6(3*2)个字节
# 第二个维度,也就是列方向上每列要跨出2个字节
(6, 2)
数组元素布局
数组扁平化后元素布局
数组内存布局
当对数组使用索引取值时,返回的是原始数组的视图,比如
v = z[::2,::2] #两个维度上,每隔1个元素取值
数组元素布局
数组扁平化后元素布局
数组内存布局
区分视图和副本
首先要区分索引和花式索引,前者返回的是原始数组的视图,后者返回的是原始数据索引后的副本。修改前者的值会影响原始数组。
z = np.zeros(9)
z_view = z[:3]
z_view[...] = 1
print(z)
out:[ 1. 1. 1. 0. 0. 0. 0. 0. 0.]
z = np.zeros(9)
z_copy = z[[0,1,2]] # 花式索引
z_copy[...] = 1
print(z)
out:[ 0. 0. 0. 0. 0. 0. 0. 0. 0.]
如果不确定索引返回的是原始数组的视图还是副本,可以通过结果的base属性确认,如果base的值是None,则返回的结果是副本。
注意某些numpy函数返回的是视图,比如ravel,而flatten返回的是副本,虽然两者都得到扁平后的数据。
代码产生的临时副本
X = np.ones(10, dtype=np.int)
Y = np.ones(10, dtype=np.int)
A = 2*X + 2*Y
运行上边的代码,除了显式的产生了X,Y和A三个数组外,还隐式的生成了两个数组2*X和2*Y。当数组非常大的时候可能会产生性能问题。解决问题的办法是显示调用numpy函数中的out参数, 明确指定输出结果到已经声明的变量。
X = np.ones(10, dtype=np.int)
Y = np.ones(10, dtype=np.int)
np.multiply(X, 2, out=X)
np.multiply(Y, 2, out=Y)
np.add(X, Y, out=X)