Python循环引用需用weakref打破、避免__del__、慎用gc.collect(),典型场景如父子节点、观察者模式、闭包隐式引用,调试宜用objgraph而非DEBUG_SAVEALL。

循环引用通常出现在对象互相持有对方的强引用时
Python 的垃圾回收器(GC)能处理大多数循环引用,但前提是这些对象没有 __del__ 方法,且不被全局变量、模块级字典或 C 扩展长期持有着。一旦出现未被及时清理的循环引用,就可能造成内存缓慢增长,尤其在长周期服务中容易暴露问题。
典型场景包括:树形结构父子节点双向引用、观察者模式中对象互相注册、缓存字典里键值互为引用等。
- 父节点保存子节点列表,子节点又通过
parent属性反向引用父节点 - 用
dict缓存函数结果,而缓存值本身又持有该dict的引用(比如闭包捕获) - 类中定义了
__del__,又参与了循环,此时 GC 会将其放入gc.garbage而不自动清理
用 weakref 打破非必需的强引用链
当某一方的引用只是“观测”或“临时访问”用途,而非生命周期依赖,就该用 weakref 替代直接赋值。它不会阻止被引用对象被回收,也天然避开循环引用问题。
例如子节点需要访问父节点但不决定其存活:
立即学习“Python免费学习笔记(深入)”;
import weakrefclass Node: def init(self, parent=None): self._parent_ref = weakref.ref(parent) if parent else None
@property def parent(self): return self._parent_ref() if self._parent_ref else None
情感家园企业站5.0 多语言多风格版下载一套面向小企业用户的企业网站程序!功能简单,操作简单。实现了小企业网站的很多实用的功能,如文章新闻模块、图片展示、产品列表以及小型的下载功能,还同时增加了邮件订阅等相应模块。公告,友情链接等这些通用功能本程序也同样都集成了!同时本程序引入了模块功能,只要在系统默认模板上创建模块,可以在任何一个语言环境(或任意风格)的适当位置进行使用!
- 不要对函数、类、模块用
weakref.ref——它们是全局常驻对象,弱引用无意义 - 避免在
__del__中调用weakref.ref()或访问已可能被销毁的对象 -
weakref.WeakKeyDictionary和weakref.WeakValueDictionary更适合缓存场景,键或值被回收后条目自动消失
gc.collect() 不是修复手段,而是诊断工具
手动调用 gc.collect() 可以触发一次完整回收,并返回本次清理的循环引用对象数量。但它不能替代设计上的解耦——频繁靠它“救场”,说明引用关系没理清。
- 调试时可加
gc.set_debug(gc.DEBUG_SAVEALL),之后未回收对象会留在gc.garbage里供检查 - 生产环境禁用
DEBUG_SAVEALL,它会导致内存泄漏(因为对象被强制保留) - 若
gc.collect()返回值持续增大,应结合gc.get_objects()或objgraph定位具体类型
闭包和装饰器里容易藏匿隐式循环引用
闭包会捕获外部作用域的变量,如果这个变量恰好是当前正在构造的对象,就构成循环。装饰器返回的 wrapper 函数若引用了被装饰实例,也一样。
常见错误写法:
def make_processor(obj):
def process():
return obj.do_something() # obj 被闭包持有
return process
若 obj 是某个实例,且 process 被存进 obj 的属性里,就成环了
- 用
functools.partial替代闭包,它不捕获整个作用域,只绑定明确参数 - 装饰器中避免把
self或实例传进闭包;改用描述符或绑定方法(types.MethodType)更可控 - 用
__slots__限制实例属性,能减少意外引用(比如误存了闭包函数到实例上)
实际项目里最难察觉的是跨模块、跨线程的间接引用,比如一个全局事件总线里注册了 A 对象的方法,而 A 又持有了总线实例——这种链路得靠 objgraph.show_backrefs() 搭配截图分析。别指望单靠 del 或 gc.collect() 解决。










