Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

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

  • PEP 3134 – 异常链和嵌入式跟踪回溯 (Python 3.0):异常的新 __context____cause__ 属性
  • PEP 415 – 使用异常属性实现上下文抑制 (Python 3.3):raise exc from None
  • PEP 409 – 抑制异常上下文(被 PEP 415 取代)

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 问题

链式异常

防止丢失异常的更改

拒绝理由

该 PEP 于 2017 年 9 月 12 日被 Victor Stinner 拒绝。在 python-dev 的讨论中决定,默认情况下不链式 C 异常,而只在有意义的地方显式链式它们。


来源:https://github.com/python/peps/blob/main/peps/pep-0490.rst

上次修改:2023-09-09 17:39:29 GMT