视图/副本
数组的数据结构
NumPy数组的数据结构由两部分构成: 一部分是实际数据的连续数据缓冲区和有关缓冲区信息的元数据. 元数据包含数据类型, 步长, 形状和其他有助于操作nd数组的信息.
Tip
- 缓冲区以连续的内存块形式存储, 这种连续存储方式提高了数据访问的速度和效率
- 步长指的是在内存中移动到下一个元素时候需要跳过的字节数
视图
通过改变特定元数据如步长和数据类型而不改变数据缓冲区中的实际数据, 可以以不同的方式访问数组. 这样会创造一种新的数据查看方式, 这些新的数组就叫做视图. 由于两个数组的数据缓冲区都是同一个, 所以对视图的任何更改都会反应在原始数组中.
注意
- 视图可以通过
[array].view()
强制创建 - 将原始数组直接赋值给一个变量和将一个视图赋值给一个变量有明显不同
- 前者的变量指向的就是原始数组, 其缓冲区和元数据的位置都没变;
- 后者的变量指向的是一个新的数组, 只不过这个新的数组的缓冲区和原始数组是同一个, 而元数据不是同一个
例子
>>> import numpy as np
>>> arr = np.arange(0, 10, dtype=np.int32)
>>> arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)
>>> arr_direct = arr
>>> arr_direct
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)
>>> arr_view = arr.view()
>>> arr_view
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)
>>> arr_view.dtype = np.dtype(np.int8) # 尝试改变后一种变量的元数据
>>> arr_view
array([0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0], dtype=int8)
>>> arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32) # 原始数组的元数据未改变
>>> arr_direct.dtype = np.dtype(np.int8) # 尝试改变前一种变量的元数据
>>> arr_direct
array([0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0], dtype=int8)
>>> arr
array([0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0], dtype=int8) # 原始的元数据改变
副本
如果新的数组是通过复制数据缓冲区和元数据创建的, 则这个新数组叫做副本. 对副本做出的更改不会反应在原始数组中. 创建副本较慢且会消耗较多内存但有时候是必要的.
注意
副本可以通过[array].copy()
强制创建.
辨认视图和副本
可以通过访问nd数组的base
属性来判断一个数组是视图还是副本:
- 如果
base
属性为原数组, 则为视图 - 如果
base
属性为None
, 则为副本
例子
-
Copies and views—NumPy v2.0 Manual. (n.d.). Retrieved June 20, 2024, from https://numpy.org/doc/stable/user/basics.copies.html ↩