浅拷贝只复制顶层对象,嵌套可变对象仍共享引用,故修改子对象会影响原对象;深拷贝递归复制所有层级但开销大、不支持循环引用及某些内置对象。

浅拷贝 copy.copy() 为什么改子对象会影响原对象
因为 copy.copy() 只复制顶层对象,内部嵌套的可变对象(比如列表里的字典、嵌套列表)仍共享引用。常见现象是:修改拷贝后对象的子元素,原对象对应位置同步变化。
适用场景:顶层结构需要独立,但内部数据允许共用(如临时读取配置、只读遍历)。
容易踩的坑:
- 误以为“复制了就完全隔离”,结果在循环中反复修改子项导致脏数据
- 对包含
set、dict、自定义类实例的嵌套结构直接用copy.copy(),未检查深层是否被修改 - 用
list[:]或list.copy()替代copy.copy()—— 它们效果相同,但仅限于列表,不通用
深拷贝 copy.deepcopy() 的开销和限制
copy.deepcopy() 递归复制所有层级,确保原对象与拷贝完全隔离。但代价明显:内存占用翻倍、执行时间随嵌套深度和体积增长。
典型问题:
- 遇到循环引用(如父对象持子引用,子又反向持父引用)会抛出
RecursionError - 无法拷贝某些内置对象:文件句柄(
io.TextIOWrapper)、线程锁(threading.Lock)、数据库连接等,会报TypeError - 自定义类若重写了
__getstate__或__reduce__,行为可能不符合预期,需手动验证
示例:以下代码会触发循环引用错误
import copy
a = {}
a['self'] = a
copy.deepcopy(a) # RecursionError: maximum recursion depth exceeded
什么时候不该用 copy.deepcopy(),而该重构设计
当频繁调用 copy.deepcopy() 且对象较大时,性能瓶颈往往不是拷贝本身,而是设计上过度依赖状态快照。
更健壮的做法:
- 把可变状态抽离为不可变数据结构(如用
dataclasses.replace()+frozen=True) - 用工厂函数生成新实例,而非拷贝旧实例(例如
Config.from_dict(new_values)) - 对只读场景,显式标注意图:用
typing.Sequence接收参数,避免意外修改 - 日志或调试需要快照?优先序列化为
json.dumps()(前提是数据可 JSON 化),比深拷贝轻量且无引用风险
自定义类如何控制拷贝行为
默认情况下,copy.copy() 和 copy.deepcopy() 都会调用类的 __copy__() 和 __deepcopy__() 方法(如果存在)。这是绕过默认递归逻辑的关键出口。
实操建议:
- 若类里有外部资源(如打开的文件、socket),应在
__copy__中抛出NotImplementedError,强制使用者明确处理 - 若部分字段应共享(如缓存字典),在
__deepcopy__中跳过它们:memo参数可用于复用已有对象 - 注意:
__deepcopy__的第二个参数是memo字典,必须传给子对象的deepcopy调用,否则破坏循环引用保护机制
示例节选:
def __deepcopy__(self, memo):
new_obj = MyType.__new__(MyType)
memo[id(self)] = new_obj # 防止递归
new_obj.data = copy.deepcopy(self.data, memo) # 显式传 memo
new_obj.cache = self.cache # 共享缓存,不拷贝
return new_obj
真正棘手的从来不是“会不会调用 copy.deepcopy()”,而是没想清楚——这个对象到底该不该被拷贝,以及拷贝后谁来负责它的生命周期。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











