一、核心概念
1.1 Python 的对象模型:一切皆对象,变量即引用
- 变量不直接存储值,而是 指向对象的引用(指针)。
id(x)返回对象的内存地址(CPython 中即PyObject*的地址)。- 赋值操作
b = a只是新增一个引用,不创建新对象。
1.2 可变性(Mutability)
| 类别 | 典型类型 | 特征 |
|---|---|---|
| 不可变对象 | int, float, str, tuple, frozenset, bytes |
一旦创建,内容不可更改;任何“修改”操作均返回新对象 |
| 可变对象 | list, dict, set, bytearray, 自定义类实例(默认) |
可原地修改内容,对象身份(id)不变 |
⚠️ 注意:
tuple虽不可变,但若其元素为可变对象(如([1], 2)),则 tuple 自身不可变,但元素内容可变。
二、三类操作在不同对象上的表现
2.1 不可变对象:三者无实质区别
1 | import copy |
原因:
copy.copy()和copy.deepcopy()对不可变对象 直接返回原对象(CPython 优化:避免无意义复制)。- 源码依据(
copy.py):
1 | def _copy_immutable(x): |
✅ 结论:对不可变对象,三种操作均指向同一内存地址;试图“修改”将创建新对象(如
a + (4,))。
2.2 可变对象(单层):浅/深拷贝创建新容器,但元素仍共享
1 | import copy |
关键疑问:
为何深拷贝后
id(a[0]) == id(b[0])?不是应该“完全独立”吗?
解答:
a[0]是整数1—— 不可变对象。deepcopy对不可变对象 不会复制,而是直接复用(与copy逻辑一致)。- Python 对小整数(
-5至256)、短字符串等启用 对象池(interning),全局复用。
验证:
1 | x = 1 |
✅ 结论:浅/深拷贝仅对可变子对象递归复制;不可变子对象始终共享。
2.3 可变对象(嵌套):浅拷贝共享子对象,深拷贝独立
1 | import copy |
验证深拷贝独立性:
1 | a[0].append(999) |
但注意子元素地址:
1 | print(id(a[0][0])) # 139731504490768 (整数 0) |
原因:
a[0][0]是整数0(不可变),deepcopy不复制不可变对象。- 即使深拷贝,不可变基本类型仍全局共享。
✅ 结论:深拷贝的“深度”仅到可变对象边界;不可变叶子节点始终复用。
三、深度机制剖析:copy 模块如何工作?
3.1 浅拷贝(copy.copy)实现逻辑
- 调用对象的
__copy__方法(若定义); - 否则,对内置可变类型:
list→x[:]或type(x)(x)dict→x.copy()set→type(x)(x)
- 不递归复制子对象 —— 新容器,旧元素引用。
3.2 深拷贝(copy.deepcopy)实现逻辑
- 维护一个
memo字典({id(obj): copy}),防止循环引用无限递归; - 查
memo,若已拷贝则直接返回; - 否则:
- 若对象有
__deepcopy__,调用之; - 否则调用
__reduce_ex__/__reduce__获取重建数据; - 对重建数据中的每个元素递归 deepcopy;
- 若对象有
- 仅当子对象为可变类型时,才创建新副本;不可变对象直接引用。
源码关键路径(简化):
1 | def deepcopy(x, memo=None): |
四、常见误区
4.1 高频误区澄清
| 误区 | 正解 |
|---|---|
| “深拷贝会复制所有对象” | 仅复制可变对象;不可变对象(如 int, str, tuple)直接复用 |
“a[:] 是深拷贝” |
仅对单层 list 是浅拷贝;对嵌套结构仍是浅拷贝 |
“dict.copy() 是深拷贝” |
是浅拷贝;值若为可变对象,仍共享 |
4.2 正确使用场景建议
| 场景 | 推荐操作 | 原因 |
|---|---|---|
| 仅需避免顶层修改影响原对象 | copy.copy() 或切片 [:] |
开销小,满足需求 |
| 容器含嵌套可变对象,需完全隔离 | copy.deepcopy() |
防止子对象修改污染 |
| 仅需复制部分字段 | 手动构造新对象(如 new_dict = {k: v for k, v in old.items() if k != 'omit'}) |
更高效、意图明确 |
| 性能敏感 + 大型嵌套结构 | 自定义 __deepcopy__ 或使用 marshal/pickle + 优化 |
避免 deepcopy 的递归开销 |
4.3 自定义类的拷贝控制
1 | class MyClass: |
⚠️ 注意:实现
__deepcopy__时务必传递memo,否则循环引用将导致栈溢出。
五、总结
| 操作 | 是否新建容器 | 是否新建可变子对象 | 是否新建不可变子对象 | 典型用法 |
|---|---|---|---|---|
**赋值 **b = a |
否 | 否 | 否 | 引用传递 |
**浅拷贝 **copy.copy(a) |
是 | 否 | 否 | 隔离顶层修改 |
**深拷贝 **copy.deepcopy(a) |
是 | 是 | 否 | 完全独立副本 |
核心原则:
- 拷贝的“深度”由子对象的可变性决定,而非操作名称。
- 不可变对象的复用是 Python 内存优化的核心策略,理解它才能避免“地址相同=未拷贝”的误判。
附:快速验证脚本模板
1 | import copy |