NumPy 2.x 完全指南【十三】复制和视图
文章目录
- 1. 引言
- 2. 视图
- 3. 复制
- 4. 判断
1. 引言
在实际应用场景中,一个输入数据往往要经过多轮处理,比如,如图像识别任务中的一张图片的主要流程有:
- 加载原始图像
- 预处理:裁剪为模型要求的格式、归一化处理等。
- 推理:输入到模型中
- 后处理
在操作 NumPy
数组时,可以通过视图直接访问内部数据缓冲区,而无需复制数据。这能确保良好的性能,但如果用户不了解这一机制,可能会导致一些不必要的问题。因此,了解这两个术语的区别,以及哪些操作返回复制,哪些操作返回视图非常重要。
NumPy
提供了复制(Copy
)和视图(View
)机制,不仅满足大规模数据处理时的内存效率,同时兼顾了数据操作的灵活性和安全性。
复制与视图的对比:
特性 | 视图(View) | 复制(Copy) |
---|---|---|
数据缓冲区 | 共享原数组数据 | 独立的新数据块 |
内存占用 | 低(仅存储元数据) | 高(复制全部数据) |
修改是否影响原数组 | 是 | 否 |
创建速度 | 快(无需复制数据) | 慢(需复制数据) |
典型操作 | 切片、reshape() 、T | copy() 、高级索引 |
2. 视图
视图(View
):一个新的数组对象,共享原始数组的数据缓冲区(即底层内存块)。
注意事项:
- 不复制数据,直接引用原数据的内存。
- 数据缓冲区保持不变,因此修改视图的数据会影响原始数组。
- 内存占用低,操作速度快。
创建视图时,仅改变某些元数据(如步幅和数据类型),而不改变数据缓冲区,视图的底层数据与原始数组共享,每个视图是独立的 ndarray
实例,拥有自己的元数据:
示例 1 ,ndarray.view()
方法可以显式创建数组的视图:
# 原始数组
arr = np.array([1, 2, 3, 4], dtype=np.int32)
view = arr.view() # 创建视图# 修改视图
view[0] = 99
print("原数组:", arr) # 输出: [99 2 3 4]
print("视图:", view) # 输出: [99 2 3 4]
当元素可以通过偏移量和步幅在原始数组中进行寻址时,视图就会被创建。因此,基础索引(如切片、整数索引)访问数组时总是会创建视图。
示例 2 ,切片生成视图:
# 原始数组
arr = np.array([1, 2, 3, 4, 5])# 切片操作
view = arr[1:4] # 生成视图
view[0] = 99 # 修改视图# 原数组被修改
print("原数组:", arr) # 输出: [1, 99, 3, 4, 5]
print("视图:", view) # 输出: [99, 3, 4]
示例 3 ,整数索引生成视图:
# 二维数组
arr_2d = np.array([[1, 2], [3, 4]])# 整数索引
view_row = arr_2d[0] # 生成视图(第一行)
view_row[0] = 99print("原数组:\n", arr_2d)
# 输出:
# [[99 2]
# [ 3 4]]
示例 4 ,当返回根据整数值索引返回单个元素时,返回的是标量值,而不是视图,修改标量不影响原数组:
# 原始数组
arr = np.array([1, 2, 3, 4, 5])
scalar = arr[0] # 返回标量(并非数组对象)
scalar = 100 # 修改标量
print(scalar) # 100
print(arr) # 输出: [1 2 3 4 5]
3. 复制
复制(Copy
):一般也翻译为拷贝,创建一个与原数组完全独立的新数组对象,包括复制数据缓冲区和元数据。
注意事项:
- 复制会创建新的数据缓冲区,与原数组完全分离。
- 修改复制后的数组不会影响原数组。
- 复制需要时间和内存(尤其是大规模数据时)。
示例 1 ,可以直接使用 ndarray.copy()
方法创建副本:
# 原始数组
arr = np.array([1, 2, 3, 4])
copy = arr.copy() # 强制创建副本# 修改副本
copy[0] = 99print("原数组:", arr) # 输出: [1 2 3 4]
print("副本:", copy) # 输出: [99 2 3 4]
示例 2 ,使用高级索引(后续会介绍)时返回的都是副本:
copy = arr[[0, 1, 2]] # 使用整数数组索引
copy[0] = 99print("原数组:", arr) # 输出: [1 2 3 4]
4. 判断
可以通过 ndarray.base
属性判断一个数组是视图还是副本。
核心规则:
- 视图的 base:指向原始数组(共享数据缓冲区)。
- 副本的 base:返回
None
(数据独立)。
示例:
# 原始数组
x = np.arange(9)
print("原始数组 x:", x)
# 创建视图
y = x.view()# 返回 True 表示 x 是 y 的 原始数组(视图关系)
print(y.base is x) # 输出: True# 新建一个数组
z = np.arange(6)
# 返回 False 表示 x 不是 z 的 原始数组(非视图关系)
print(z.base is x) # 输出: False# 返回 None 表示 z 没有原始数组(独立副本)
print(z.base) # 输出: None
还可以使用 np.shares_memory(arr1, arr2)
检查两个数组是否共享数据,示例:
print(np.shares_memory(x, y)) # True
print(np.shares_memory(y, z)) # False