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 编写的扩展中则不是。

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

  • 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-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