PEP 490 – 在 C 层面链接异常
- 作者:
- Victor Stinner <vstinner at python.org>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2015-03-25
- Python 版本:
- 3.6
摘要
在 C 层面链接异常,这在 Python 层面已经实现。
基本原理
Python 3 引入了一项杀手级新功能:异常默认是链接的,PEP 3134。
示例
try:
raise TypeError("err1")
except TypeError:
raise ValueError("err2")
输出
Traceback (most recent call last):
File "test.py", line 2, in <module>
raise TypeError("err1")
TypeError: err1
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test.py", line 4, in <module>
raise ValueError("err2")
ValueError: err2
异常在 Python 代码中是默认链接的,但在用 C 编写的扩展中则不是。
Python 3.4.3 和 3.5 版本中引入了一个新的私有函数 _PyErr_ChainExceptions() 用于链接异常。目前,必须显式调用它来链接异常,并且它的使用并不简单。
来自 zipimport 模块使用 _PyErr_ChainExceptions() 的示例,将之前的 OSError 链接到一个新的 ZipImportError 异常
PyObject *exc, *val, *tb;
PyErr_Fetch(&exc, &val, &tb);
PyErr_Format(ZipImportError, "can't open Zip file: %R", archive);
_PyErr_ChainExceptions(exc, val, tb);
此 PEP 提议在 C 层面也自动链接异常,以保持一致性并提供更多故障信息以帮助调试。先前的示例将变得简单
PyErr_Format(ZipImportError, "can't open Zip file: %R", archive);
提案
修改 PyErr_*() 函数以链接异常
修改 Python C API 中引发异常的 C 函数,以自动链接异常:修改 PyErr_SetString()、PyErr_Format()、PyErr_SetNone() 等。
修改函数以不链接异常
当新异常包含前一个异常的信息,甚至更多信息时,保留前一个异常并不总是很有用,尤其是当两个异常具有相同的类型时。
使用 int(str) 的无用异常链接示例
TypeError: a bytes-like object is required, not 'type'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string, a bytes-like object or a number, not 'type'
新的 TypeError 异常比之前的异常包含更多信息。应该隐藏之前的异常。
可以在引发新异常之前调用 PyErr_Clear() 函数来清除当前异常,以避免将当前异常与新异常链接。
修改函数以链接异常
一些函数会保存然后恢复当前异常。如果引发了新异常,该异常目前会显示在 sys.stderr 或根据函数被忽略。其中一些函数应该被修改为链接异常。
忽略新异常的函数示例
ptrace_enter_call(): 忽略异常subprocess_fork_exec(): 忽略 enable_gc() 引发的异常_thread模块的t_bootstrap(): 忽略在尝试将引导函数显示到sys.stderr时引发的异常PyDict_GetItem(),_PyDict_GetItem_KnownHash(): 忽略在字典中查找键时引发的异常_PyErr_TrySetFromCause(): 忽略异常PyFrame_LocalsToFast(): 忽略dict_to_map()引发的异常_PyObject_Dump(): 忽略异常。_PyObject_Dump()用于调试,检查正在运行的进程,它不应修改 Python 状态。Py_ReprLeave(): 忽略异常“因为没有办法报告它们”type_dealloc(): 忽略remove_all_subclasses()引发的异常PyObject_ClearWeakRefs(): 忽略异常?call_exc_trace(),call_trace_protected(): 忽略异常remove_importlib_frames(): 忽略异常do_mktuple(), 例如Py_BuildValue()使用的辅助函数: 忽略异常?flush_io(): 忽略异常sys_write(),sys_format(): 忽略异常_PyTraceback_Add(): 忽略异常PyTraceBack_Print(): 忽略异常
将新异常显示到 sys.stderr 的函数示例
atexit_callfuncs(): 使用PyErr_Display()显示异常并返回最后一个异常,该函数调用多个回调,只返回最后一个异常sock_dealloc(): 使用PyErr_WriteUnraisable()记录ResourceWarning异常slot_tp_del(): 使用PyErr_WriteUnraisable()显示异常_PyGen_Finalize(): 使用PyErr_WriteUnraisable()显示gen_close()异常slot_tp_finalize(): 使用PyErr_WriteUnraisable()显示__del__()方法引发的异常PyErr_GivenExceptionMatches(): 使用PyErr_WriteUnraisable()显示PyType_IsSubtype()引发的异常
向后兼容性
链接异常的一个副作用是异常会存储 traceback 对象,traceback 对象会存储 frame 对象,frame 对象会存储局部变量。局部变量因异常而被保留。一个常见问题是局部变量和异常之间的引用循环:异常存储在局部变量中,而间接存储在异常中的 frame。该循环仅影响存储异常的应用程序。
现在可以使用 Python 3.5 中引入的新 traceback.TracebackException 对象来修复此引用循环。它存储格式化完整文本 traceback 所需的信息,而无需存储局部变量。
asyncio 模块会受到引用循环问题的影响。该模块也由 Python 标准库之外维护,以便为 Python 3.3 发布版本。traceback.TracebackException 可能会回溯到一个私有的 asyncio 模块以修复引用循环问题。
备选方案
无更改
新的私有函数 _PyErr_ChainExceptions() 足以手动链接异常。
异常将仅在有意义的地方显式链接。
新增链接异常的辅助函数
像 PyErr_SetString() 这样的函数不会自动链接异常。为了使 _PyErr_ChainExceptions() 的使用更容易,新增了私有函数
_PyErr_SetStringChain(exc_type, message)_PyErr_FormatChain(exc_type, format, ...)_PyErr_SetNoneChain(exc_type)_PyErr_SetObjectChain(exc_type, exc_value)
用于引发特定异常的辅助函数,例如 _PyErr_SetKeyError(key) 或 PyErr_SetImportError(message, name, path) 不会链接异常。应该使用通用的 _PyErr_ChainExceptions(exc_type, exc_value, exc_tb) 来与这些辅助函数链接异常。
附录
PEP
Python C API
头文件 Include/pyerror.h 声明了与异常相关的函数。
引发异常的函数
PyErr_SetNone(exc_type)PyErr_SetObject(exc_type, exc_value)PyErr_SetString(exc_type, message)PyErr_Format(exc, format, ...)
引发特定异常的辅助函数
PyErr_BadArgument()PyErr_BadInternalCall()PyErr_NoMemory()PyErr_SetFromErrno(exc)PyErr_SetFromWindowsErr(err)PyErr_SetImportError(message, name, path)_PyErr_SetKeyError(key)_PyErr_TrySetFromCause(prefix_format, ...)
管理当前异常
PyErr_Clear(): 清除当前异常,相当于except: passPyErr_Fetch(exc_type, exc_value, exc_tb)PyErr_Restore(exc_type, exc_value, exc_tb)PyErr_GetExcInfo(exc_type, exc_value, exc_tb)PyErr_SetExcInfo(exc_type, exc_value, exc_tb)
其他异常处理函数
PyErr_ExceptionMatches(exc): 检查以实现except exc: ...PyErr_GivenExceptionMatches(exc1, exc2)PyErr_NormalizeException(exc_type, exc_value, exc_tb)_PyErr_ChainExceptions(exc_type, exc_value, exc_tb)
Python 问题
链接异常
- Issue #23763: 在 C 中链接异常
- Issue #23696: zipimport: 将 ImportError 链接到 OSError
- Issue #21715: 在 C 层面链接异常: 添加了
_PyErr_ChainExceptions() - Issue #18488: sqlite: 如果 step() 方法调用失败,用户函数的 finalize() 方法可能会在设置了异常的情况下被调用
- Issue #23781: 在 2.7 中添加私有 _PyErr_ReplaceException()
- Issue #23782: _PyTraceback_Add 中的泄漏
防止异常丢失的更改
拒绝
PEP 已于 2017-09-12 被 Victor Stinner 拒绝。在 python-dev 讨论中决定,C 异常默认不链接,而是仅在有意义的地方显式链接。
版权
本文档已置于公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0490.rst
最后修改: 2025-02-01 08:59:27 GMT