使用Pytest/Mock捕获异常时对象引用残留导致__del__不触发问题求助
pytest + Mock 中__del__方法未触发的问题解决
问题重现
先给出可复现的测试代码,和你描述的场景一致:
import pytest from unittest.mock import Mock class DelTest: def __init__(self): self.deleted = False def __del__(self): self.deleted = True def test_del_passes(): del_test = DelTest() test_fn = Mock(return_value="ok") test_fn() del test_fn del del_test assert del_test.deleted # 执行正常 def test_del_fails(): del_test = DelTest() test_fn = Mock(side_effect=TimeoutError) try: test_fn() except TimeoutError: pass del test_fn del del_test assert del_test.deleted # 断言失败,__del__未触发
原因分析
当Mock抛出异常时,Python的异常traceback会保留对Mock对象的引用,而pytest在测试流程中会暂存这些traceback信息用于错误报告,导致即使你执行del test_fn,Mock对象的引用计数仍不为0。如果你的DelTest对象和Mock存在间接引用链(或者测试上下文的残留引用),就会导致del_test的引用计数无法降至0,__del__方法也就不会被触发。
解决方案
方案1:强制触发垃圾回收
在断言前手动调用gc.collect(),强制Python执行垃圾回收,处理残留引用:
import gc import pytest from unittest.mock import Mock class DelTest: def __init__(self): self.deleted = False def __del__(self): self.deleted = True def test_del_fixed(): del_test = DelTest() test_fn = Mock(side_effect=TimeoutError) try: test_fn() except TimeoutError: pass del test_fn del del_test gc.collect() # 强制触发垃圾回收 assert del_test.deleted
方案2:清除异常traceback引用
直接移除traceback中保留的引用,让对象的引用计数正常归零:
import sys import pytest from unittest.mock import Mock class DelTest: def __init__(self): self.deleted = False def __del__(self): self.deleted = True def test_del_fixed(): del_test = DelTest() test_fn = Mock(side_effect=TimeoutError) try: test_fn() except TimeoutError: pass # 清除残留的traceback引用 if hasattr(sys, 'last_traceback'): del sys.last_traceback del test_fn del del_test assert del_test.deleted
额外建议
尽量避免依赖__del__方法执行关键操作,因为其触发时机由Python垃圾回收机制决定,存在不确定性。更可靠的方式是使用**上下文管理器(with语句)**来显式管理资源释放:
class DelTest: def __init__(self): self.deleted = False def close(self): self.deleted = True def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() def test_with_context(): with DelTest() as del_test: test_fn = Mock(side_effect=TimeoutError) try: test_fn() except TimeoutError: pass assert del_test.deleted # 无论是否抛异常,都会触发close
内容的提问来源于stack exchange,提问作者Markus A.
相关产品推荐
相关产品推荐

