最可靠方式是在测试函数内用tracemalloc做前后快照对比:启动→执行→采样→过滤→对比,辅以objgraph验证引用链,禁用gc可放大泄漏;psutil.rss上升不等于python层泄漏。

直接用 tracemalloc 在测试函数内做前后快照对比,是最可靠的方式。闭包泄漏本质是对象被意外强引用而无法释放,必须从分配源头定位,不能依赖进程级内存(如 psutil.RSS)——它噪声大、不可归因。
在测试函数中启动和采样内存追踪
把 tracemalloc 的生命周期严格绑定到单个测试函数内,避免跨测试污染:
- 在测试开始前调用
tracemalloc.start(25)(25 层栈深度足够定位到业务代码行) - 执行待测逻辑(比如调用一个带闭包的装饰器或生成器)
- 立即调用
snapshot = tracemalloc.take_snapshot() - 用
filter_traces过滤掉site-packages、pytest、importlib等无关路径,只保留你的模块
构造可验证的闭包泄漏场景
写一个“故意泄漏”的最小闭环,用于验证检测逻辑是否有效:
- 定义一个大对象(如
big_data = [0] * 10**6) - 用闭包捕获它:
def make_closure(): return lambda: big_data - 调用
make_closure()后不保存返回值——按理应被回收;若泄漏,则该对象仍出现在快照中 - 对比两次快照:一次在闭包创建前,一次在作用域退出后,看
big_data对应的分配是否残留
用 objgraph 辅助验证引用链
当 tracemalloc 发现可疑分配后,用 objgraph 查具体谁在持有着:
- 在泄漏点后插入
objgraph.show_most_common_types(limit=20),看是否有异常多的function、cell或自定义类实例 - 对疑似对象执行
objgraph.show_backrefs([obj], max_depth=4),图示化展示谁引用了它 - 重点检查
cell对象是否反向指向外部大对象(这是闭包泄漏的典型痕迹)
避免常见误判陷阱
很多“看似泄漏”其实是假阳性:
- 不要在
pytest_runtest_makereport钩子里测内存——时机不可控,容易漏掉关键分配或误抓清理阶段 - 禁用 GC(
gc.disable())再跑测试,能放大循环引用问题,让泄漏更易暴露(CI 中推荐) - 如果
tracemalloc没新增记录,但psutil.RSS上升,大概率是操作系统未及时回收物理页,不是 Python 层泄漏











