闭包导致内存泄漏的本质是强引用链阻碍垃圾回收,解决需四步:确认捕获对象、用值替代对象、对必要对象用weakref.ref包装、配合生命周期及时清理。

明确闭包到底捕获了什么
先确认问题是否存在,而不是盲目加 weakref:
- 打印
func.__code__.co_freevars查看它引用了哪些自由变量名 - 检查
func.__closure__中每个 cell 对应的值:cell.cell_contents - 如果看到大对象(如
<mywidget object at></mywidget>或长度上万的 list),说明确实在强引用
用值代替对象,切断强引用
最轻量、最安全的做法:不捕获整个对象,只捕获真正需要的字段或计算结果
- ❌ 错误写法:
lambda: self.process()→ 强引用self - ✅ 正确写法:
lambda: process_with_id(self.id, self.name)→ 只传不可变值 - 对类方法做回调时,优先用
weakref.WeakMethod(obj.method),它自动处理self的弱引用
必须持对象时,用弱引用包装目标,而非闭包
weakref.ref() 不该套在闭包函数上(多数闭包不可弱引用,且失去自由变量环境就失效),而应作用于它依赖的那个外部对象
- ✅ 正确模式:
ref = weakref.ref(large_obj),然后在闭包里写if ref(): ref().do_something() - ⚠️ 注意:弱引用存进全局 dict 时,要确保 key 可哈希;若用
WeakKeyDictionary,自定义类需实现__hash__和__eq__ - ❌ 避免:
weakref.ref(lambda: large_obj.data)—— lambda 创建时已绑定large_obj,弱引用毫无意义
配合生命周期管理,及时清理引用链
闭包常用于事件监听、定时器、缓存装饰器等场景,泄漏多因“注册了却没注销”
- 给回调函数配一个 cleanup 函数,在组件卸载、任务结束时调用:
removeEventListener('click', handler) - 装饰器中缓存无限增长?加 TTL 或最大容量限制,或改用
functools.lru_cache(maxsize=128) - 全局字典存闭包结果?定期清理过期项,或改用
weakref.WeakValueDictionary(值是弱引用)











