copy.copy() 在拷贝含嵌套可变对象(如list、dict)的实例时会静默出错,修改副本的嵌套对象将同步影响原对象;copy.deepcopy() 遇不可序列化对象(如锁、文件句柄)明确抛typeerror,且需自定义__deepcopy__处理资源字段。

copy.copy() 什么时候会静默出错
它不报错,但改了拷贝体的嵌套可变对象,原对象也跟着变——这是最危险的“静默错误”。典型现象:shallow_copied_list[2].append(1) 后,original_list[2] 也多了一个元素。
常见触发场景:
- 类实例中含
list、dict或自定义可变对象作为属性,且后续会调用.append()、.update()、__setitem__等原地修改方法 - 用
list.copy()或[:]拷贝子类实例,结果返回的是list而非原类型(比如MyList变成list) - 误以为
copy.copy()和x.copy()行为一致——其实后者是类型方法,不支持嵌套字典等复合结构
copy.deepcopy() 的 TypeError 不是偶然
它会在遇到不可序列化对象时明确抛出 TypeError: cannot pickle _thread.RLock objects 这类错误,不是 bug,而是设计使然。deepcopy 本质依赖 pickle 协议,而文件句柄、锁、socket、C 扩展对象等天然无法被 pickle。
必须注意的限制:
- 循环引用能处理,但开销陡增:memo 字典要持续哈希查重,大数据结构下内存和时间成本显著上升
- 自定义类若含资源型字段(如
open('file.txt')返回的 file 对象),默认 deepcopy 会失败 -
json.loads(json.dumps(x))不是替代方案:它会丢掉 tuple、datetime、自定义类、NaN、Inf 等非 JSON 类型,且对空字典/列表的类型保持也不可靠
自定义类必须实现 __deepcopy__ 才安全
默认 deepcopy() 会递归拷贝 self.__dict__ 里所有值,但一旦字段是线程锁、数据库连接或缓存引用,就可能崩溃或造成资源泄漏。
正确写法要点:
- 必须接收
memo参数,并在递归调用copy.deepcopy(v, memo)时传入,否则循环引用检测失效 - 对不该拷贝的字段(如单例、全局配置、已打开的 socket),手动跳过或替换为原引用
- 如果类还支持浅拷贝定制,应同时实现
__copy__(self),避免依赖copy.copy()的默认行为
示例片段:
def __deepcopy__(self, memo):<br> new_obj = self.__class__.__new__(self.__class__)<br> memo[id(self)] = new_obj<br> for k, v in self.__dict__.items():<br> if k == '_lock': # 跳过线程锁<br> setattr(new_obj, k, v)<br> else:<br> setattr(new_obj, k, copy.deepcopy(v, memo))<br> return new_obj
别忽略 copy.replace() 这个新选项
Python 3.13 加入的 copy.replace() 不是拷贝,而是“构造新实例 + 替换字段”,适用于数据类(@dataclass)或命名元组场景,开销远低于 deepcopy(),且完全绕过不可序列化问题。
适用条件明确:
- 对象是
dataclass、NamedTuple或实现了__replace__方法的类 - 你只需要改几个字段,其余全部复用原值(包括不可拷贝对象)
- 不需要隔离嵌套可变对象——因为没做拷贝,只是新建容器
比如:new_cfg = copy.replace(old_cfg, timeout=30, retries=5),比 deepcopy 快一个数量级,且不会因 old_cfg.db_conn 是活连接而失败。
真正难选的从来不是“用哪个函数”,而是“这个对象里哪些东西该共享、哪些必须隔离”。深拷贝不是银弹,浅拷贝也不是缺陷——它们只是不同层级的引用控制策略。动手前先画两笔内存图,比查文档更快定位风险点。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











