You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 18:25:29