本文系《pytest源码剖析》系列内容
31. unraisableexception
插件路径:_pytest.unraisableexception
实现的 hook
调用的 hook
无
定义的 fixture
无
插件功能
创建上下文管理器catch_unraisable_exception
并在在以下 hook 包装器中使用:
pytest_runtest_setup
pytest_runtest_call
pytest_runtest_teardown
代码片段
def unraisable_exception_runtest_hook() -> Generator[None, None, None]: with catch_unraisable_exception() as cm: yield if cm.unraisable: print('xxxxxxxxx') if cm.unraisable.err_msg is not None: err_msg = cm.unraisable.err_msg else: err_msg = "Exception ignored in" msg = f"{err_msg}: {cm.unraisable.object!r}\n\n" msg += "".join( traceback.format_exception( cm.unraisable.exc_type, cm.unraisable.exc_value, cm.unraisable.exc_traceback, ) ) warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
通过上下文管理器的方式,收集无法引发的异常,并发出警告
简评
unraisablehook是 python 3.8 新增的一个钩子,在 python 遇到” 无法引发的异常 “时自动调用。
通过设置该 hook,可以自助处理“无法引发的异常 ”。
本插件正是让 pytest 设置了这个 python 的钩子,在遇到这些情况时发出警告
...
所谓“无法引发的异常 ”(或可译作“未抛出的异常”?)是一种在 Python 无法将其报告给调用者时发生的错误
例如:对象终结器错误 (__del__())、弱引用回调失败、GC 收集期间出错
请看这样的代码
assert False
这是一个引发【断言异常】的代码,引起引发了一次,python 退出代码为 1
Traceback (most recent call last): File "E:\PyProject\pytest_7.2.x\aaaaa.py", line 1, in assert FalseAssertionError
Process finished with exit code 1
如果把它放在类的__del__方法中:
class A:
def __del__(self): assert False
A()
执行结果
Exception ignored in: <function A.__del__ at 0x000001DE612D53A0>Traceback (most recent call last): File "E:\PyProject\pytest_7.2.x\aaaaa.py", line 4, in __del__ assert FalseAssertionError:
Process finished with exit code 0
它引发异常了吗?
没有。
证据有二:
退出代码为 0 (表示正常退出)
输出了一行字Exception ignored异常被忽略
这个异常,就是无法引发的异常
...
这样的异常不能被处理(或记录)吗?
当然可以 ,python 3.8 新增的 hookunraisablehook就是为此而生。
接下来我们实现这个 hook
import sys
def sanmu_funcs(unraisable: "sys.UnraisableHookArgs"): # 创建函数处理 print('异常类型', unraisable.exc_type) print('引发位置', unraisable.exc_traceback.tb_frame.f_code) print('局部变量', unraisable.exc_traceback.tb_frame.f_locals)
sys.unraisablehook = sanmu_funcs # 注册钩子
执行结果
异常类型<class >引发位置 <\code object __del__ at 0x000002A16F42DE40, file "D:\pytest_7.2.x\aaa.py", line 16>局部变量 {'self': <__main__.A object at 0x000002A16F56DC10>}
Process finished with exit code 0
...
本插件通过上下文管理器的方式,分别在用例执行的三个阶段进行处理:
setup
call
teardown
用例执行阶段出现了了“无法引发的异常”,会在pytest运行结束后统一发出警告
领取专属 10元无门槛券
私享最新 技术干货