Python中的浅拷贝和深拷贝
这个问题问得特别到位!浅拷贝和深拷贝是Python里一个"看似简单,但一写代码就懵"的核心概念。很多老手都在这里踩过坑。
我用一个特别生活化的场景给你讲明白,保证一听就懂!
1. 先看一个"悲剧"故事(引子)
假设你有一个嵌套列表(列表里套列表):
原版菜单 = ['汤', ['鱼', '肉'], '甜点']
现在你想给朋友拷贝一份菜单,让他随便改,不影响你的原版。
结果你用了最直接的方式:
朋友的菜单 = 原版菜单 # 这是赋值,不是拷贝!
朋友的菜单[1][0] = '素鸡' # 把鱼改成素鸡
print(原版菜单)
# 输出:['汤', ['素鸡', '肉'], '甜点'] → 你的原版也被改了!😱
为什么? 因为=只是给同一个对象贴了个新标签,根本没复制。这就是引用赋值。
2. 浅拷贝:只Copy第一层
浅拷贝(Shallow Copy):就像你复印一份文件,只复印了第一页的目录,目录里写的"第二章内容详见第50页",但第50页的内容还是共用同一份。
Python里怎么实现?
import copy
原版 = ['汤', ['鱼', '肉'], '甜点']
浅拷贝版 = copy.copy(原版) # 或者用 原版.copy() 或 原版[:]
看看效果:
浅拷贝版[0] = '粥' # 改第一层(字符串)→ 完全OK,互不影响
浅拷贝版[1][0] = '素鸡' # 改第二层(嵌套列表)→ 问题来了!
print(原版) # ['汤', ['素鸡', '肉'], '甜点'] → 原版被影响了!
print(浅拷贝版) # ['粥', ['素鸡', '肉'], '甜点']
图解浅拷贝(灵魂画手上线):
原版 -----> [ '汤', [地址A], '甜点' ]
|
浅拷贝版 --> [ '汤', [地址A], '甜点' ]
注意:两个列表的[1]位置指向的是同一个地址A(嵌套列表)
所以你改地址A里的内容,两个都变!
3. 深拷贝:彻底分家,各过各的
深拷贝(Deep Copy):就像从头到尾全部重新抄写一遍,连书里引用的附录都重新抄一份。两份完全独立,互不干扰。
Python里怎么实现?
import copy
原版 = ['汤', ['鱼', '肉'], '甜点']
深拷贝版 = copy.deepcopy(原版)
看看效果(这次终于安全了):
深拷贝版[0] = '粥'
深拷贝版[1][0] = '素鸡' # 随便改!
print(原版) # ['汤', ['鱼', '肉'], '甜点'] → 纹丝不动!✅
print(深拷贝版) # ['粥', ['素鸡', '肉'], '甜点']
图解深拷贝:
原版 -----> [ '汤', [地址A], '甜点' ]
|
深拷贝版 --> [ '汤', [地址B], '甜点' ]
注意:Python重新创建了一个全新的嵌套列表(地址B)
两份完全独立,你改你的,我改我的!
4. 核心结论(记小本本)
| 操作方式 | 是否拷贝嵌套对象 | 适用场景 |
|---|---|---|
赋值 = |
不拷贝,直接引用 | 只是想给对象起个别名 |
浅拷贝 .copy() / copy.copy() |
只拷贝第一层,嵌套的仍共享 | 对象结构简单,没有嵌套,或嵌套对象不可变(如字符串、元组) |
深拷贝 copy.deepcopy() |
递归拷贝所有层,完全独立 | 对象结构复杂,有嵌套列表/字典,且需要完全独立修改 |
5. 一个容易踩的坑(必看!)
如果嵌套对象是"不可变类型"(如字符串、元组),浅拷贝就够用了,因为不可变类型没法改内容,只能重新赋值。
原版 = ['汤', '肉', '甜点'] # 注意:没有嵌套列表了!
浅拷贝版 = 原版.copy()
浅拷贝版[1] = '素鸡' # 这是重新赋值,不是修改原对象
print(原版) # ['汤', '肉', '甜点'] → 没事!因为字符串不可变
判断技巧:只要你用 对象[索引] = 新值 这种赋值操作,就是在替换引用;只有用 对象[索引][子索引] = 新值 这种链式修改,才会影响共享的嵌套对象。
6. 什么时候用深拷贝?(实战场景)
- 缓存数据:你从数据库查出一份数据,想缓存一份快照,同时另一份拿去给业务逻辑修改。
- 多线程/多进程:多个线程要独立修改同一份初始数据。
- 游戏开发:一个角色模板,要复制出多个独立属性的副本(每个角色有自己的装备列表)。
7. 给你的小作业(动手试试!)
import copy
a = [1, 2, [3, 4]]
b = a.copy() # 浅拷贝
c = copy.deepcopy(a) # 深拷贝
# 请问:执行下面这行后,a、b、c分别是什么?
b[2][0] = 999
# 答案:a和b都变成 [1, 2, [999, 4]],c保持不变 [1, 2, [3, 4]]
你可以把这段代码敲进编辑器跑一下,亲眼看看结果。亲眼看到效果,比背一百遍概念都管用!
如果还有疑问,比如"什么时候浅拷贝反而更高效"或者"自定义类怎么实现深拷贝",随时来问!这种底层概念搞懂了,后面看源码、写框架都会轻松很多。加油!💪
浙公网安备 33010602011771号