Python · 3 分钟阅读
深拷贝与浅拷贝
在 Python 中,赋值 并不复制对象——它只是把同一个对象的另一个 名字
贴到一个对象上。真正的复制有两种:浅拷贝 和 深拷贝。理解它们的区别
能避免掉进“改了 A,结果 B 也跟着变”的坑。
1. 赋值 ≠ 拷贝
a = [1, 2, [3, 4]]
b = a # 同一对象
b.append(5)
print(a) # [1, 2, [3, 4], 5] —— a 跟着变了
print(a is b) # True —— 身份相同
=不会调用copy.copy,它只是让两个名字指向同一个对象。要“真的复制”,
必须显式调用copy/deepcopy(或使用切片、构造、推导式等语言机制)。
2. 浅拷贝
新对象,但里面的 子对象仍然共享。
import copy
original = [1, 2, [3, 4]]
shallow = copy.copy(original) # 等价于 original[:]
shallow.append(99) # 只影响 shallow
shallow[2][0] = 'changed' # 影响 original!
print(original) # [1, 2, ['changed', 4]]
print(shallow) # [1, 2, ['changed', 4], 99]
浅拷贝的常见等价写法:
| 写法 | 适用对象 |
|---|---|
copy.copy(x) |
任意对象 |
x[:] |
列表 / 字符串 |
list(x) / dict(d) / set(s) |
构造方式复制 |
x.copy() |
列表 / 字典 / 集合 |
copy.copy(x) |
自定义对象 |
3. 深拷贝
新对象 + 所有子对象递归地也是新的。
import copy
original = [1, 2, [3, 4]]
deep = copy.deepcopy(original)
deep[2][0] = 'changed'
print(original) # [1, 2, [3, 4]] —— 不受影响
print(deep) # [1, 2, ['changed', 4]]
deepcopy会沿着__dict__、__slots__、容器递归。如果对象里持有
外部资源(文件、socket、线程…)或者循环引用,库函数都能正确处理。
4. 图示
赋值(a = b):
a ─┐
├──▶ [1, 2, ━► [3, 4]] (同一对象)
b ─┘
浅拷贝(b = a.copy()):
a ──▶ [1, 2, ━► [3, 4]]
↑ ↑
b ──▶ [1, 2] ─┘ │
子对象共用
深拷贝(b = deepcopy(a)):
a ──▶ [1, 2, ─► [3, 4]]
b ──▶ [1, 2, ─► [3', 4']] (完全独立)
5. 常见误区
5.1 列表里装同样的子对象
a = [[0]] * 3
a[0][0] = 9
print(a) # [[9], [9], [9]] —— 三个子列表是同一个!
# 正确写法
a = [[0] for _ in range(3)]
5.2 字典里装可变默认值
import copy
d = {"k": [1, 2, 3]}
d2 = d.copy()
d2["k"].append(4) # 浅拷贝:原 dict 的 list 也会被改
print(d) # {'k': [1, 2, 3, 4]}
d3 = copy.deepcopy(d)
d3["k"].append(5) # 深拷贝:互不影响
print(d) # {'k': [1, 2, 3, 4]}
print(d3) # {'k': [1, 2, 3, 4, 5]}
5.3 自定义对象的 __copy__ / __deepcopy__
想让自定义类的复制行为更精细,可以重写:
import copy
class Connection:
def __init__(self, url):
self.url = url
self.session = open_session() # 假设是个不可复制的资源
def __copy__(self):
new = self.__class__(self.url)
new.session = None # 不复制 session
return new
def __deepcopy__(self, memo):
new = self.__class__(self.url)
new.session = None
memo[id(self)] = new
return new
6. 性能
deepcopy 比 copy 慢,因为它要 递归 复制每层。对于纯不可变对象,Python
会做一定优化:例如 deepcopy((1, 2, 3)) 其实只是返回了原对象——因为元组 +
数字都是不可变的。
import copy, sys
big = [list(range(1000)) for _ in range(1000)]
%timeit copy.copy(big) # 几十微秒
%timeit copy.deepcopy(big) # 几十毫秒
真要复制一个上百万元素的嵌套结构时,先考虑是否真的需要深拷贝——
也许你只需要一个临时浅拷贝 + 按需 deepcopy 某个子结构。
7. 选型建议
| 场景 | 推荐 |
|---|---|
| 顶层不可变(int / str / tuple / frozenset) | 随便用 = / 浅拷贝 |
| 顶层可变,但只读不改 | copy / [:] |
| 要把子对象彻底隔离 | deepcopy |
| 自定义类(持有资源) | 重写 __copy__ / __deepcopy__ |
| 用 JSON 序列化作为“深拷贝” | 看 JSON 替代方案 |
8. JSON 替代方案
如果对象里只装“基本类型 + 字典 + 列表 + 字符串”,可以借 json 当成
deepcopy:简单、快、跨语言可移植。
import json
obj = {"users": [{"name": "a", "tags": [1, 2]}]}
clone = json.loads(json.dumps(obj)) # “深拷贝”
# 缺点:tuple -> list,datetime -> str,set 没法处理。
9. 小结
a = b不是复制,是同一对象的另一个名字。- 浅拷贝:复制一层,子对象共享。
- 深拷贝:递归复制,所有层级独立。
- 选哪种看业务:会不会改子对象?会,就
deepcopy。 - 自定义类可以重写
__copy__/__deepcopy__精细控制复制行为。