Python 中的深拷贝(deep copy) 和 浅拷贝(shallow copy) 是非常容易混淆但又极其重要的一对概念,尤其在处理嵌套的可变对象(list、dict、set 等)时,一不小心就会出现“改一个地方,另一个地方也变了”的 bug。
下面用最直白的方式 + 代码 + 内存图思路帮你彻底搞懂(2026 年最新写法也没变化,核心机制从 Python 2.x 到 3.12+ 都一样)。
一、先搞清楚三种“复制”方式的区别
| 方式 | 写法 | 是否创建新对象 | 嵌套对象是否共享引用 | 修改嵌套对象是否互相影响 | 适用场景 |
|---|---|---|---|---|---|
| 直接赋值 | b = a | 否 | 共享 | 是 | 只是想多个名字指向同一个对象 |
| 浅拷贝 | copy.copy() 或 a[:] / list(a) / dict(a) | 是 | 共享 | 是 | 只想复制最外层,内层可以共享 |
| 深拷贝 | copy.deepcopy() | 是 | 不共享(递归复制) | 否 | 想要完全独立的两份数据 |
二、经典代码对比(强烈建议你自己敲一遍)
import copy
# ----------------- 准备数据 -----------------
original = [1, 2, [3, 4], {"name": "重阳"}]
# 方式1:直接赋值(最容易出错的新手写法)
a1 = original
a1[2][0] = 999
print(original) # [1, 2, [999, 4], {'name': '重阳'}] ← 原数据也被改了!
# 方式2:浅拷贝(常见面试题考察点)
a2 = copy.copy(original) # 或 a2 = original[:] / list(original)
a2[2][0] = 888
print(original) # [1, 2, [888, 4], {'name': '重阳'}] ← 嵌套列表还是被改了!
print(a2) # [1, 2, [888, 4], {'name': '重阳'}]
a2[0] = 777 # 修改最外层
print(original[0]) # 还是 1(外层不共享)
# 方式3:深拷贝(真正独立)
a3 = copy.deepcopy(original)
a3[2][0] = 666
a3[3]["name"] = "Grok"
print(original) # [1, 2, [888, 4], {'name': '重阳'}] ← 原数据完全不动!
print(a3) # [1, 2, [666, 4], {'name': 'Grok'}]
三、用内存图直观理解(面试/debug 神器)
原始对象:
original ──► [1, 2, 列表A, 字典B]
▲ ▲
│ │
└─────┬───┘
│
浅拷贝 a2 ──► [新列表] │ ← 外层是新的,内层指向同一个列表A 和 字典B
│
深拷贝 a3 ──► [全新列表] ──┼─► [全新 666, 4] ← 所有层级都重新创建
└─► [全新 {"name":"Grok"}]
四、常见实现浅拷贝的方式(面试常考)
lst = [[1,2], [3,4]]
# 以下都是浅拷贝
shallow1 = lst[:] # 切片
shallow2 = list(lst) # 构造器
shallow3 = copy.copy(lst)
shallow4 = lst.copy() # Python 3.3+ 列表自带方法(推荐)
# 字典同理
d = {"a": [1,2]}
shallow_d = d.copy() # 字典自带浅拷贝
五、深拷贝的特殊情况 & 坑(2026 年仍需注意)
- 循环引用(自己包含自己)
a = [1, 2]
a.append(a) # a 包含了自己
b = copy.deepcopy(a) # 能正确处理循环引用,不会无限递归
- 自定义类(需要实现
__copy__/__deepcopy__才能完美拷贝)
- 普通类默认浅拷贝只复制引用
- 深拷贝会递归调用对象的
__deepcopy__(如果没实现就用默认行为)
- 函数、模块、文件对象 等不可序列化的东西
deepcopy会报错或返回原对象(视情况)
- 性能:深拷贝非常慢,尤其是大数据结构 → 优先考虑是否真的需要深拷贝
六、总结一句话口诀(背下来就不会错)
- 想改外层不影响原对象 → 浅拷贝就够(最常用)
- 想改里层也不影响原对象 → 必须深拷贝
- 只是多个变量指向同一个东西 → 直接用
=
最实用建议:
- 99% 的业务场景用
.copy()或list()/dict()就够了 - 真正需要完全独立副本(比如配置模板、测试数据)才上
deepcopy - 调试时多用
id()查看对象是否相同:id(a[2]) == id(original[2])
有哪部分还觉得模糊?
想看更多例子(比如嵌套多层字典、自定义类拷贝、deepcopy 性能对比)?
或者想直接来几道面试题练手?随时说,我继续陪你过这一关。