Python的秘密生活:Copy Cat(Deep Copy)
Source: Dev.to
深拷贝 vs. 切片:哪一种真正保护了你的数据?
🎧 音频版: 想听吗?在 YouTube 查看本次深度探讨的 AI 播客完整版。
📺 视频版: 想看吗?在 YouTube 查看 7 分钟的可视化解释。
Timothy 面色苍白。当 Margaret 拿着一壶新冲好的伯爵茶走进来时,他甚至没有抬头。
“Margaret,我看到鬼了,” Timothy 低声说。“我在为棋社即将到来的锦标赛运行一个模拟。我做了一个 练习赛程,这样我就可以在不动 正式赛程 的情况下测试一些选手的走位。但…当我修改练习版时,正式版自己也变了。”
他把代码给她看:
# The Official Bracket: A list of teams (nested lists)
official_bracket = [["Alex", "Alice"], ["Bob", "Barbara"]]
# Timothy makes a "Practice" copy using a slice
practice_bracket = official_bracket[:]
# He swaps a player in the first match of the practice bracket
practice_bracket[0][0] = "Timothy"
print(f"Practice: {practice_bracket}")
print(f"Official: {official_bracket}")
输出
Practice: [['Timothy', 'Alice'], ['Bob', 'Barbara']]
Official: [['Timothy', 'Alice'], ['Bob', 'Barbara']]
“看见了吗?” Timothy 指着屏幕说。“我根本没动
official_bracket[0][0]。我只动了练习副本。但改变跟着我来了。真是机器里的幽灵。”
地址的复印件
Margaret 拉过一把椅子。
“这不是幽灵,Timothy。是 浅拷贝。你以为自己在复印文件,实际上只是在复印一串地址。”
当你使用 official_bracket[:] 时,Python 会创建一个 新的外层列表(一个新信封),但内部的列表(各场比赛)仍然是 相同的对象。通过一个列表修改内部元素,会同时改变另一个列表看到的同一对象。
浅层的局限
“我以为切片
[:]是复制列表的标准方式?我们在第 19 集里就用它修复了跳过循环的 bug!”
它在 扁平列表(例如字符串或数字列表)上表现完美。一旦列表中包含其他可变对象,切片只会复制最外层,内部引用仍然共享。
深层解决方案
要获得完全独立的副本,使用 copy.deepcopy:
import copy
official_bracket = [["Alex", "Alice"], ["Bob", "Barbara"]]
# The Deep Solution
practice_bracket = copy.deepcopy(official_bracket)
# Now swap the player
practice_bracket[0][0] = "Timothy"
print(f"Official: {official_bracket[0][0]}") # Alex
print(f"Practice: {practice_bracket[0][0]}") # Timothy
deepcopy 会遍历每一个嵌套对象,沿途创建新的拷贝。它 彻底但更耗时耗内存,因此应只在嵌套数据结构中使用。
Margaret 的速查表:复制猫
| 操作 | 功能 | 适用范围 |
|---|---|---|
赋值 (b = a) | 不产生拷贝;两个名字指向同一对象 | — |
浅拷贝 (b = a[:] 或 b = a.copy()) | 只拷贝最外层容器 | 扁平列表(字符串、数字) |
深拷贝 (b = copy.deepcopy(a)) | 递归拷贝所有嵌套对象 | 任意嵌套结构(列表、字典、自定义对象) |
小技巧: 要验证内部对象是否共享,比较它们的标识:
id(official_bracket[0]) == id(practice_bracket[0]) # True → 仍然共享
如果结果是 True,说明你仍在使用浅拷贝。
在下一集,Margaret 和 Timothy 将面对 “默认陷阱”——Timothy 将了解到给函数设置默认列表就像和陌生人共用牙刷一样。