NumPy 数组的复制的几种实现方法
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">1 无复制(No Copy at All)</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">1.1 简单赋值:同一对象的多个名称</a></li><li><a href="#_lab2_0_1">1.2 函数调用的引用传递</a></li></ul><li><a href="#_label1">2 视图 / 浅复制(View or Shallow Copy)</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_2">2.1 view()方法创建视图</a></li><li><a href="#_lab2_1_3">2.2 视图的 “形状独立,数据共享”</a></li><li><a href="#_lab2_1_4">2.3 数组切片返回视图</a></li></ul><li><a href="#_label2">3 深复制(Deep Copy)</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_5">3.1 copy()方法创建深复制</a></li><li><a href="#_lab2_2_6">3.2 深复制的数据独立性</a></li><li><a href="#_lab2_2_7">3.3 大数组切片的内存优化</a></li></ul><li><a href="#_label3">4 总结:三种方式对比</a></li><ul class="second_class_ul"></ul></ul></div><p>在 NumPy 中操作数组时,理解数据的复制机制是避免逻辑错误和内存浪费的关键。新手常因混淆 “复制” 与 “引用” 而踩坑,本文将系统讲解三种场景:无复制、视图(浅复制)和深复制。</p><p class="maodian"><a name="_label0"></a></p><h2>1 无复制(No Copy at All)</h2>
<p>简单赋值或函数传参时,不会复制数组对象或其数据,只是对同一对象的 “重命名” 或 “引用传递”。</p>
<p class="maodian"><a name="_lab2_0_0"></a></p><h3>1.1 简单赋值:同一对象的多个名称</h3>
<div class="jb51code"><pre class="brush:py;">import numpy as np
# 定义原始数组
a = np.array([,
,
])
b = a# 无新对象创建,b是a的“别名”
print(b is a)# 输出 True,证明是同一对象
</pre></div>
<p>此时修改 b 会直接改变 a,因为它们指向同一块内存。</p>
<p class="maodian"><a name="_lab2_0_1"></a></p><h3>1.2 函数调用的引用传递</h3>
<p>Python 中可变对象(如 NumPy 数组)以引用方式传递给函数,函数内的操作会影响原对象。</p>
<p class="maodian"><a name="_label1"></a></p><h2>2 视图 / 浅复制(View or Shallow Copy)</h2>
<p>视图会创建新的数组对象,但共享原始数组的数据。新数组是原数据的 “窗口”,数据本身未被复制。</p>
<p class="maodian"><a name="_lab2_1_2"></a></p><h3>2.1 view()方法创建视图</h3>
<div class="jb51code"><pre class="brush:py;">c = a.view()
print(c is a) # 输出 False,c是新对象
print(c.base is a) # 输出 True,c的底层数据由a持有
print(c.flags.owndata)# 输出 False,c不“拥有”自己的数据
</pre></div>
<p class="maodian"><a name="_lab2_1_3"></a></p><h3>2.2 视图的 “形状独立,数据共享”</h3>
<p>视图可以独立修改形状,不影响原数组;但修改数据会同步影响原数组。</p>
<div class="jb51code"><pre class="brush:py;">c = c.reshape((2, 6))# 修改c的形状
print(a.shape) # 原数组a形状仍为(3, 4)
c = 1234 # 修改c的数据
print(a)
# 输出:
# array([[ 0, 1, 2, 3],
# ,
# [ 8, 9, 10, 11]])
</pre></div>
<p class="maodian"><a name="_lab2_1_4"></a></p><h3>2.3 数组切片返回视图</h3>
<p>对数组切片时,返回的是原数组的视图,而非新数组。</p>
<div class="jb51code"><pre class="brush:py;">s = a[:, 1:3]# 切片得到视图s
s[:] = 10 # 修改视图的数据(注意是s[:] = 10,不是s = 10)
print(a)
# 输出:
# array([[ 0, 10, 10, 3],
# ,
# [ 8, 10, 10, 11]])
</pre></div>
<p>注意:s[:] = 10 是修改视图数据,而 s = 10 是将 s 重新赋值为新对象,不再关联原数组。</p>
<p class="maodian"><a name="_label2"></a></p><h2>3 深复制(Deep Copy)</h2>
<p>深复制会创建原数组及其数据的完整副本,新数组拥有独立的内存空间,与原数组完全解耦。</p>
<p class="maodian"><a name="_lab2_2_5"></a></p><h3>3.1 copy()方法创建深复制</h3>
<div class="jb51code"><pre class="brush:py;">d = a.copy()
print(d is a) # 输出 False,d是新对象
print(d.base is a) # 输出 False,d与a无任何共享
</pre></div>
<p class="maodian"><a name="_lab2_2_6"></a></p><h3>3.2 深复制的数据独立性</h3>
<p>修改深复制的数组不会影响原数组。</p>
<div class="jb51code"><pre class="brush:py;">d = 9999
print(a)# 原数组a不受影响,输出与之前一致
</pre></div>
<p class="maodian"><a name="_lab2_2_7"></a></p><h3>3.3 大数组切片的内存优化</h3>
<p>当原始数组很大,且仅需要其一小部分时,对切片进行深复制可释放原始数组的内存。</p>
<div class="jb51code"><pre class="brush:py;">a = np.arange(int(1e8))# 创建超大数组
b = a[:100].copy() # 对切片深复制,b拥有独立数据
del a # 可释放a占用的大量内存
</pre></div>
<p>如果用 b = a[:100](视图),a 会被 b 引用,即使 del a 也无法释放内存。</p>
<p class="maodian"><a name="_label3"></a></p><h2>4 总结:三种方式对比</h2>
<table><thead><tr><th>类型</th><th>是否创建新对象</th><th>是否共享数据</th><th>操作 / 方法</th><th>数据修改影响</th></tr></thead><tbody><tr><td>无复制</td><td>否</td><td>是</td><td>简单赋值、函数传参</td><td>原数组与新变量相互影响</td></tr><tr><td>视图(浅复制)</td><td>是</td><td>是</td><td>view()、数组切片</td><td>数据修改相互影响,形状修改不影响原数组</td></tr><tr><td>深复制</td><td>是</td><td>否</td><td>copy()</td><td>原数组与新数组完全独立</td></tr></tbody></table>
<p>掌握这三种机制,能让你在处理 NumPy 数组时更精准地控制内存和数据一致性,写出更健壮的代码。</p> 感谢楼主的详细分享!
这篇文章写得非常清晰,把NumPy数组复制这个容易混淆的概念讲得很透彻。我之前就是因为没搞懂view和copy的区别,在处理大数据时吃了不少亏。
补充几点个人心得:
1. 关于内存警告
有时候修改大数组的视图时,NumPy会给出警告,虽然不会报错,但数据确实被改了。这个细节新手很容易忽略,楼主的例子很好地说明了这一点。
2. 实际应用场景
我在图像处理时经常用到切片视图,比如:
img = np.array(...)# 读取图片
region = img# 取ROI
region[:] = 0# 局部遮罩
这时候就要注意region是视图,直接修改会影响原图。
3. 性能小贴士
如果是临时需要查看数据又不想影响原数组,可以先用.copy(),虽然会占用额外内存,但能避免很多bug。调试阶段不要省这个内存,发布时再考虑优化。
总结:简单赋值=取别名;view()=开窗户看同一份数据;copy()=复印一份独立的。
再次感谢楼主的整理,收藏了!以后新人问这个问题直接甩链接~ :lol
頁:
[1]