weakref 能解决循环引用导致的内存泄漏和析构异常问题;它通过弱引用避免对象间强引用闭环,使 GC 可正常回收对象,并提供 finalize 等机制替代不稳定的 __del__。

weakref 能解决什么问题
Python 的垃圾回收器(GC)主要靠引用计数,但遇到循环引用时——比如 A 持有 B 的引用,B 又持有 A 的引用——引用计数永远不会降到 0,对象就无法被及时释放。如果这些对象里有 __del__ 方法,还可能触发 ResourceWarning 或直接报错:Exception ignored in: <function a.__del__ at></function>。这不是内存泄漏的全部表现,但析构异常往往是最先暴露的问题。
什么时候必须用 weakref.ref 而不是 weakref.WeakKeyDictionary
取决于你控制哪一端的生命周期:
- 如果是「父对象持有子对象,但子对象不该反向强引用父对象」,比如回调中闭包捕获了父实例,那就该用
weakref.ref 包一层:import weakref
</li></ul><p>class Parent:
def <strong>init</strong>(self):
self.child = Child(self)</p><p>class Child:
def <strong>init</strong>(self, parent):
self._parent_ref = weakref.ref(parent) # 不是 parent!</p><pre class="brush:php;toolbar:false;">def do_something(self):
parent = self._parent_ref() # 调用后才取值
if parent is not None:
parent.update_status()
-
WeakKeyDictionary 适合「多个对象共享一个弱引用映射表」场景,比如缓存:键是对象实例,值是计算结果;一旦实例被回收,对应条目自动消失。但它不能帮你打破单个对象间的循环引用链。
__del__ 里调用 weakref.ref 是危险操作
这是最容易踩的坑:在 __del__ 中访问 self._parent_ref(),很可能返回 None,甚至抛出 ReferenceError——因为被弱引用的对象可能已经进入析构流程,其 __del__ 已执行或正在执行。
- 不要在
<strong>del</strong> 中做任何依赖弱引用对象状态的操作
- 更稳妥的做法是:把清理逻辑移到显式方法中,比如
close() 或 teardown(),由上层控制调用时机
- 如果必须用
<strong>del</strong>,只做最轻量的事(如打日志),且对弱引用调用加 try/except ReferenceError
用 weakref.finalize 替代部分 __del__ 场景weakref.finalize 是更可控的资源清理方式,它不绑定到某个对象的生命周期,而是监听对象是否“即将不可达”:
- 它在对象被 GC 回收前触发,但不会和
<strong>del</strong> 争抢执行顺序
- 支持传入任意可调用对象,参数也支持绑定
- 不会因异常中断整个析构流程(异常会被静默丢弃,除非设
exitstack=True)
import weakref
<p>def cleanup_handler(obj_id):
print(f"Cleaning up resource for {obj_id}")</p><p>class ManagedResource:
def <strong>init</strong>(self, obj_id):
self.obj_id = obj_id
self._finalizer = weakref.finalize(self, cleanup_handler, obj_id)
注意:finalize 注册的是对当前对象的弱引用,所以它本身不会阻止对象被回收;但如果回调里又强引用了该对象(比如存到全局列表),就会导致对象永远不被回收。
弱引用不是银弹。真正难处理的,是跨模块、多线程、带 C 扩展对象的循环引用——这时候得结合 gc.get_referrers() 和 objgraph 去定位源头,而不是只靠换一个 ref 类型。
weakref.ref 而不是 weakref.WeakKeyDictionary
取决于你控制哪一端的生命周期:
- 如果是「父对象持有子对象,但子对象不该反向强引用父对象」,比如回调中闭包捕获了父实例,那就该用
weakref.ref包一层:import weakref </li></ul><p>class Parent: def <strong>init</strong>(self): self.child = Child(self)</p><p>class Child: def <strong>init</strong>(self, parent): self._parent_ref = weakref.ref(parent) # 不是 parent!</p><pre class="brush:php;toolbar:false;">def do_something(self): parent = self._parent_ref() # 调用后才取值 if parent is not None: parent.update_status()-
WeakKeyDictionary适合「多个对象共享一个弱引用映射表」场景,比如缓存:键是对象实例,值是计算结果;一旦实例被回收,对应条目自动消失。但它不能帮你打破单个对象间的循环引用链。
__del__里调用weakref.ref是危险操作 这是最容易踩的坑:在__del__中访问self._parent_ref(),很可能返回None,甚至抛出ReferenceError——因为被弱引用的对象可能已经进入析构流程,其__del__已执行或正在执行。- 不要在
<strong>del</strong>中做任何依赖弱引用对象状态的操作 - 更稳妥的做法是:把清理逻辑移到显式方法中,比如
close()或teardown(),由上层控制调用时机 - 如果必须用
<strong>del</strong>,只做最轻量的事(如打日志),且对弱引用调用加try/except ReferenceError
用
weakref.finalize替代部分__del__场景weakref.finalize是更可控的资源清理方式,它不绑定到某个对象的生命周期,而是监听对象是否“即将不可达”:- 它在对象被 GC 回收前触发,但不会和
<strong>del</strong>争抢执行顺序 - 支持传入任意可调用对象,参数也支持绑定
- 不会因异常中断整个析构流程(异常会被静默丢弃,除非设
exitstack=True)
import weakref <p>def cleanup_handler(obj_id): print(f"Cleaning up resource for {obj_id}")</p><p>class ManagedResource: def <strong>init</strong>(self, obj_id): self.obj_id = obj_id self._finalizer = weakref.finalize(self, cleanup_handler, obj_id)注意:
finalize注册的是对当前对象的弱引用,所以它本身不会阻止对象被回收;但如果回调里又强引用了该对象(比如存到全局列表),就会导致对象永远不被回收。弱引用不是银弹。真正难处理的,是跨模块、多线程、带 C 扩展对象的循环引用——这时候得结合
gc.get_referrers()和objgraph去定位源头,而不是只靠换一个 ref 类型。 -


















