进阶篇 · Advanced
lv.3 进阶
kp-028
深浅拷贝与对象相等性
1. 一句话定义
浅拷贝只复制最外层容器(内层对象仍共享),深拷贝递归复制全部嵌套结构;== 比值、is 比身份——四者共同构成 Python 的"复制与相等"语义。
2. 为什么重要
这是 kp-003(名字绑定)的进阶篇:几乎所有"改了 A、B 也跟着变"的灵异 bug,根源都在浅拷贝误解或 is/== 混用。
3. 前置知识
4. 核心概念
python
import copy
a = [[1, 2], [3, 4]]
b1 = a.copy() # 浅拷贝(等价 list(a) / a[:] / list(a))
b2 = copy.deepcopy(a) # 深拷贝
a[0].append(9)
b1[0] # [1, 2, 9] ← 内层共享,被波及
b2[0] # [1, 2] ← 完全独立浅拷贝三兄弟等价:a.copy()、list(a)、a[:]——都只复制一层。
5. 原理与机制
text
a ──► 外层[ ] ──► [1,2] b1(浅) ──► 新外层[ ] ──► 同一个 [1,2] ✗共享
└────► [3,4] b2(深) ──► 新外层[ ] ──► 新 [1,2]、新 [3,4] ✓独立相等性三层:
python
x = [1, 2]; y = [1, 2]
x == y # True (__eq__:值相等)
x is y # False (身份不同)
x is x # True
# 自定义类:__eq__ 决定 ==,__hash__ 决定能否进 set/dict
# 不定义 __eq__ 时默认按身份比较(object.__eq__ 即 is 语义)哈希联动铁律:a == b 必须推出 hash(a) == hash(b);frozen dataclass / NamedTuple / tuple 自动满足,手写类要自己保证(kp-019)。
6. 关键事实(模型/图示)
text
选择决策树:
需要"新容器、内层独立"? → copy.deepcopy(慢,注意循环引用已内置处理)
只需新外壳、内层本就不可变? → 浅拷贝(快,够用)
只是多一个名字? → b = a(kp-003)7. 直观类比
浅拷贝是复印了文件夹封面:里面每份文件(内层对象)还是原件,谁改谁都看得见;深拷贝是连文件带附件整套复印,从此两套互不相干。
8. 实例与案例
python
# 经典 bug:用浅拷贝"备份"配置
defaults = {"theme": {"dark": True}, "lang": "zh"}
user = defaults.copy()
user["lang"] = "en" # 安全(顶层新键)
user["theme"]["dark"] = False # 灾难!defaults["theme"]["dark"] 也变了
# 修复:copy.deepcopy(defaults) 或 dict(config) 的专深拷贝写法9. 常见误区
b = a以为有拷贝发生 —— 只是名字绑定(kp-003);要独立从copy()开始。- 浅拷贝用于嵌套结构 —— 上例;嵌套可变内容必须 deepcopy。
- 在自定义类里定义
__eq__却忘了__hash__—— 实例从此不可哈希(进不了 set)。 - deepcopy 全依赖 —— deepcopy 慢且对含锁/连接的对象不安全;考虑不可变数据结构或显式重建。
10. 自测题
a = [[1]]; b = a[:],执行b[0].append(2)后a是什么?x == y为 True 时x is y一定为 False 吗?举例反例。- 为什么可变默认值陷阱(kp-007)是浅拷贝问题的近亲?
参考答案
[[1, 2]]——浅拷贝共享内层列表。- 不一定:小整数、被缓存的短字符串等"同一对象"既
==也is(如a=256; b=256时a is b为 True,是实现细节)。 - 都源于"多个名字共享同一个可变对象":默认值跨调用共享、浅拷贝跨嵌套共享。
11. 与其他知识点的关系
12. 延伸阅读
- copy 模块文档:https://docs.python.org/zh-cn/3/library/copy.html