copy.deepcopy能安全处理循环引用因其内置memo机制;错误自定义__deepcopy__、手工递归或eval(repr())会绕过保护导致栈溢出;正确做法是直接调用且自定义时必须传递memo参数。

直接用 copy.deepcopy 就能安全处理绝大多数循环引用场景,它内部自带循环检测机制;栈溢出通常是因为你绕过了这个保护,比如自定义了 __deepcopy__ 却没传 memo 参数,或用了手工递归、eval(repr()) 等错误方式。
先确认是不是循环引用导致的
打印对象结构或用 id() 观察引用关系。常见情况包括:
- 父对象持有子对象列表,子对象又反向保存父引用
- 两个类实例互相持有对方引用
- 字典或列表嵌套自身(如
d = {}; d['self'] = d)
如果手动顺着属性链访问会无限深入(比如 obj.a.b.c.a.b.c...),基本可以断定是循环引用。
优先信任内置 deepcopy
Python 标准库的 copy.deepcopy 已通过 memo 字典记录已拷贝对象的 id,自动跳过重复节点。只要你不干扰它,就不用额外处理。
- ✅ 正确做法:
import copy; new_obj = copy.deepcopy(original_obj) - ❌ 避免重写
__reduce__或__getstate__后未处理闭环 - ❌ 不要用
eval(repr(obj))或自己写递归函数替代深拷贝
自定义 __deepcopy__ 时必须传 memo
如果你为类实现了 __deepcopy__,它必须接收且传递 memo 参数,否则会绕过内置保护,触发无限递归。
- ✅ 正确写法:在新建实例后立刻注册
memo[id(self)] = new_obj,后续调用copy.deepcopy(x, memo) - ❌ 错误写法:调用
copy.deepcopy(self.parent)漏掉memo,会导致二次进入原对象
极端情况下的备用方案
当对象含不可哈希类型、C 扩展对象或 memo 失效时,可考虑:
- 用
json.dumps(obj, default=str)转字符串再json.loads(),彻底切断引用(但丢失方法、类型和私有属性) - 换用
dill库:比pickle更强,支持更多对象类型,也内置循环引用处理 - 手动剪枝:遍历前标记循环节点,拷贝时跳过反向引用字段(适合业务逻辑清晰的结构)











