python 的 copy.deepcopy 内置循环引用防护机制,通过 memo 缓存避免死循环;错误自定义 __deepcopy__(如漏传 memo)或绕过它才导致栈溢出。

直接用 Python 标准库的 copy.deepcopy 就能安全处理绝大多数循环引用,它内部自带 memo 缓存机制,根本不会陷入死循环。所谓“堆栈溢出死循环灾难”,往往是因为手动破坏了这个保护逻辑,或者误用了不带循环检测的自定义实现。
确认是不是真由循环引用触发
别急着写代码,先验证问题根源:
- 用
id(obj)打印关键对象的内存地址,看是否反复出现同一 id - 手动顺着引用链走几层:比如
obj.parent.children[0].parent→ 如果能绕回起点,就是循环 - 用
objgraph快速定位:安装后运行objgraph.show_backrefs([obj], max_depth=3),图谱里出现闭环箭头即为确认
优先信任内置 deepcopy,别绕开它
Python 的 copy.deepcopy 默认就启用循环引用防护——靠一个 memo 字典记下每个已拷贝对象的 id() 和对应副本。只要你不干扰它,它就能稳住。
- ✅ 正确用法:
import copy; new = copy.deepcopy(old) - ❌ 常见踩坑:自己写递归函数、用
eval(repr(obj))、重写__deepcopy__却漏传memo参数 - ⚠️ 特别注意:若类中定义了
__reduce__或__getstate__,必须确保它们不隐式重建引用闭环,否则 memo 也救不了
自定义 __deepcopy__ 时必须传 memo
如果你为类实现了 __deepcopy__,这是最容易引爆栈溢出的地方——不传 memo,等于关掉安全阀。
- ✅ 必须这样写:
def __deepcopy__(self, memo): ... copy.deepcopy(self.attr, memo) - ❌ 错误示范:
copy.deepcopy(self.attr)(没传 memo,会重新开始一轮无保护递归) - ? 提前注册新实例:
memo[id(self)] = new_obj这一步不能少,否则子字段拷贝时仍可能重复处理当前对象
实在不行,换策略:序列化或迭代拷贝
当对象含不可哈希类型、C 扩展对象,或你明确知道结构但想彻底规避递归,可切换路径:
-
转 JSON 再重建:适合纯数据对象,
json.loads(json.dumps(obj, default=str)),断掉所有引用,但丢方法和类型 -
用 dill 库:比 pickle 更强,原生支持循环引用和复杂对象,
import dill; new = dill.loads(dill.dumps(obj)) - 手写迭代式拷贝:用显式栈代替函数调用栈,完全避开系统递归限制,适合超深嵌套+循环混合场景









