在 numpy 中操作数组时,理解数据的复制机制是避免逻辑错误和内存浪费的关键。新手常因混淆 “复制” 与 “引用” 而踩坑,本文将系统讲解三种场景:无复制、视图(浅复制)和深复制。
1 无复制(no copy at all)
简单赋值或函数传参时,不会复制数组对象或其数据,只是对同一对象的 “重命名” 或 “引用传递”。
1.1 简单赋值:同一对象的多个名称
import numpy as np
# 定义原始数组
a = np.array([[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11]])
b = a # 无新对象创建,b是a的“别名”
print(b is a) # 输出 true,证明是同一对象
此时修改 b 会直接改变 a,因为它们指向同一块内存。
1.2 函数调用的引用传递
python 中可变对象(如 numpy 数组)以引用方式传递给函数,函数内的操作会影响原对象。
2 视图 / 浅复制(view or shallow copy)
视图会创建新的数组对象,但共享原始数组的数据。新数组是原数据的 “窗口”,数据本身未被复制。
2.1 view()方法创建视图
c = a.view() print(c is a) # 输出 false,c是新对象 print(c.base is a) # 输出 true,c的底层数据由a持有 print(c.flags.owndata) # 输出 false,c不“拥有”自己的数据
2.2 视图的 “形状独立,数据共享”
视图可以独立修改形状,不影响原数组;但修改数据会同步影响原数组。
c = c.reshape((2, 6)) # 修改c的形状 print(a.shape) # 原数组a形状仍为(3, 4) c[0, 4] = 1234 # 修改c的数据 print(a) # 输出: # array([[ 0, 1, 2, 3], # [1234, 5, 6, 7], # [ 8, 9, 10, 11]])
2.3 数组切片返回视图
对数组切片时,返回的是原数组的视图,而非新数组。
s = a[:, 1:3] # 切片得到视图s s[:] = 10 # 修改视图的数据(注意是s[:] = 10,不是s = 10) print(a) # 输出: # array([[ 0, 10, 10, 3], # [1234, 10, 10, 7], # [ 8, 10, 10, 11]])
注意:s[:] = 10 是修改视图数据,而 s = 10 是将 s 重新赋值为新对象,不再关联原数组。
3 深复制(deep copy)
深复制会创建原数组及其数据的完整副本,新数组拥有独立的内存空间,与原数组完全解耦。
3.1 copy()方法创建深复制
d = a.copy() print(d is a) # 输出 false,d是新对象 print(d.base is a) # 输出 false,d与a无任何共享
3.2 深复制的数据独立性
修改深复制的数组不会影响原数组。
d[0, 0] = 9999 print(a) # 原数组a不受影响,输出与之前一致
3.3 大数组切片的内存优化
当原始数组很大,且仅需要其一小部分时,对切片进行深复制可释放原始数组的内存。
a = np.arange(int(1e8)) # 创建超大数组 b = a[:100].copy() # 对切片深复制,b拥有独立数据 del a # 可释放a占用的大量内存
如果用 b = a[:100](视图),a 会被 b 引用,即使 del a 也无法释放内存。
4 总结:三种方式对比
| 类型 | 是否创建新对象 | 是否共享数据 | 操作 / 方法 | 数据修改影响 |
|---|---|---|---|---|
| 无复制 | 否 | 是 | 简单赋值、函数传参 | 原数组与新变量相互影响 |
| 视图(浅复制) | 是 | 是 | view()、数组切片 | 数据修改相互影响,形状修改不影响原数组 |
| 深复制 | 是 | 否 | copy() | 原数组与新数组完全独立 |
掌握这三种机制,能让你在处理 numpy 数组时更精准地控制内存和数据一致性,写出更健壮的代码。
到此这篇关于numpy 数组的复制的几种实现方法的文章就介绍到这了,更多相关numpy 数组复制内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论