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 编写的扩展中则不是。
一个新的私有 _PyErr_ChainExceptions()
函数在 Python 3.4.3 和 3.5 中被引入,用于链式异常。目前,必须显式调用它来链式异常,并且它的用法并不简单。
_PyErr_ChainExceptions()
用法的示例,来自 zipimport
模块,将之前的 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()
引发的异常
向后兼容性
链式异常的一个副作用是异常存储跟踪回溯对象,跟踪回溯对象存储帧对象,帧对象存储局部变量。局部变量由异常保持存活。一个常见的问题是局部变量和异常之间的引用循环:异常存储在局部变量中,并且帧间接存储在异常中。循环仅影响存储异常的应用程序。
现在可以使用 Python 3.5 中引入的新 traceback.TracebackException
对象修复引用循环。它存储格式化完整文本跟踪回溯所需的信息,而无需存储局部变量。
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)
来使用这些辅助函数链式异常。
附录
PEPs
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: pass
PyErr_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 年 9 月 12 日被 Victor Stinner 拒绝。在 python-dev 的讨论中决定,默认情况下不链式 C 异常,而只在有意义的地方显式链式它们。
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0490.rst