修改或重新引发 C API 中的 Python 错误
Modifying or Reraising Python error in C API
我有一些代码试图将对象解析为整数:
long val = PyLong_AsLong(obj);
if(val == -1 && PyErr_Occurred()) {
return -1;
}
这里 obj
是普通的 PyObject *
,如果 obj
不是整数,PyLong_AsLong
会引发一个非常通用的 TypeError
。
我想将错误消息转换成更具信息性的内容,因此我想修改现有的错误对象,或者重新提出它。
我目前的解决方案是:
long val = PyLong_AsLong(obj);
if(val == -1 && PyErr_Occurred()) {
PyErr_Clear();
PyErr_Format(PyExc_TypeError, "Parameter must be an integer type, but got %s", Py_TYPE(obj)->tp_name);
return -1;
}
这是重新引发错误的正确方法吗?具体来说,
- 我需要打电话给
PyErr_Clear
吗?我怀疑它正确地减少了现有的异常对象,但我不确定。
- 我可以修改当时已经抛出的错误消息而不重新引发它吗?
- 是否有一个选项可以执行与
raise new_err from old_err
相同的操作?
我不确定如何在这种情况下使用 PyErr_SetExcInfo
,尽管我的直觉告诉我它可能以某种方式相关。
您现有的代码很好,但如果您想执行异常链接的等效操作,则可以。如果您想跳至如何操作,请跳至答案结尾附近的第 3 点。
要解释如何执行修改传播异常或执行等同于 raise Something() from existing_exception
的操作,首先,我们必须解释异常状态在 C 级别是如何工作的。
传播异常由 per-thread error indicator consisting of a type, value, and traceback. That sounds a lot like sys.exc_info()
表示,但它并不相同。 sys.exc_info()
用于 被 Python 级代码捕获 的异常,而不是仍在传播的异常。
错误指示符可能是未规范化,这基本上意味着构造异常对象的工作还没有执行,值 错误指示器中不是异常 type 的实例。这种状态是为了效率而存在的;如果在需要规范化之前错误指示器被 PyErr_Clear
清除,Python 将跳过引发异常的大部分工作。异常规范化由 PyErr_NormalizeException
, with a bit of extra work in PyException_SetTraceback
执行以设置异常对象的 __traceback__
属性。
PyErr_Clear
有点像 except
块的 C 等价物,但它只是清除错误指示器,而不会让您检查很多异常信息。要捕获异常并检查它,您需要 PyErr_Fetch
。 PyErr_Fetch
就像捕获异常并检查 sys.exc_info()
,但它不会设置 sys.exc_info()
或规范化异常。它清除错误指示器并直接为您提供错误指示器的原始内容。
显式异常链接 (raise Something() from existing_exception
) 通过 PyException_SetCause
将新异常的 __cause__
设置为现有异常。这两个异常都需要异常对象,所以如果你想从 C 中做同样的事情,你必须规范化异常并自己调用 PyException_SetCause
。
隐式异常链接(raise Something()
在 except
块中)通过 PyException_SetContext
将新异常的 __context__
设置为现有异常。类似于PyException_SetCause
,这需要异常对象和异常规范化。 except
块中的 raise Something() from existing_exception
实际上设置了 __cause__
和 __context__
,如果你想在 C 级别执行显式异常链接,你通常应该这样做。
- 据我所知,技术上没有必要,但无论如何这样做可能是个好主意。看起来
PyErr_Format
和其他设置错误指示器的函数将首先清除错误指示器(如果已经设置),但大多数函数都没有记录。
- 有点,但这可能是个坏主意。您可以规范化错误指示器并设置异常对象的
message
属性,但这不会影响 args
或异常 class 可能对其参数所做的任何其他事情,这可能会导致奇怪的问题。或者,您可以使用 PyErr_Fetch
获取错误指示器并使用 PyErr_Restore
的值的新字符串恢复它,但是如果有一个现有的异常对象,它会丢弃它,并且它会做出关于异常 class 的签名。
是的,这是可能的,但是通过 public C API 函数来完成它是相当笨拙和手动的。您必须手动执行大量规范化、取消引发和引发异常。
There are efforts to make C-level exception chaining more convenient, but so far, the more convenient functions are all considered internal. For example, _PyErr_FormatFromCause
类似于 PyErr_Format
,但它将新异常链接到现有的传播异常(通过 __context__
和 __cause__
.
我暂时不建议直接调用它;它是非常新的(3.6+),而且很可能会发生变化(具体来说,如果看到它在新的 Python 版本中失去其前导下划线,我不会感到惊讶)。相反,复制 implementation of _PyErr_FormatFromCause
/_PyErr_FormatVFromCause
(and respecting the license) 是确保您拥有正确的规范化和链接的繁琐位的好方法。
如果您想在 C 级别执行隐式(仅 __context__
)异常链接,它也是一个有用的工作参考 - 只需删除处理 __cause__
.[=56= 的部分]
我有一些代码试图将对象解析为整数:
long val = PyLong_AsLong(obj);
if(val == -1 && PyErr_Occurred()) {
return -1;
}
这里 obj
是普通的 PyObject *
,如果 obj
不是整数,PyLong_AsLong
会引发一个非常通用的 TypeError
。
我想将错误消息转换成更具信息性的内容,因此我想修改现有的错误对象,或者重新提出它。
我目前的解决方案是:
long val = PyLong_AsLong(obj);
if(val == -1 && PyErr_Occurred()) {
PyErr_Clear();
PyErr_Format(PyExc_TypeError, "Parameter must be an integer type, but got %s", Py_TYPE(obj)->tp_name);
return -1;
}
这是重新引发错误的正确方法吗?具体来说,
- 我需要打电话给
PyErr_Clear
吗?我怀疑它正确地减少了现有的异常对象,但我不确定。 - 我可以修改当时已经抛出的错误消息而不重新引发它吗?
- 是否有一个选项可以执行与
raise new_err from old_err
相同的操作?
我不确定如何在这种情况下使用 PyErr_SetExcInfo
,尽管我的直觉告诉我它可能以某种方式相关。
您现有的代码很好,但如果您想执行异常链接的等效操作,则可以。如果您想跳至如何操作,请跳至答案结尾附近的第 3 点。
要解释如何执行修改传播异常或执行等同于 raise Something() from existing_exception
的操作,首先,我们必须解释异常状态在 C 级别是如何工作的。
传播异常由 per-thread error indicator consisting of a type, value, and traceback. That sounds a lot like sys.exc_info()
表示,但它并不相同。 sys.exc_info()
用于 被 Python 级代码捕获 的异常,而不是仍在传播的异常。
错误指示符可能是未规范化,这基本上意味着构造异常对象的工作还没有执行,值 错误指示器中不是异常 type 的实例。这种状态是为了效率而存在的;如果在需要规范化之前错误指示器被 PyErr_Clear
清除,Python 将跳过引发异常的大部分工作。异常规范化由 PyErr_NormalizeException
, with a bit of extra work in PyException_SetTraceback
执行以设置异常对象的 __traceback__
属性。
PyErr_Clear
有点像 except
块的 C 等价物,但它只是清除错误指示器,而不会让您检查很多异常信息。要捕获异常并检查它,您需要 PyErr_Fetch
。 PyErr_Fetch
就像捕获异常并检查 sys.exc_info()
,但它不会设置 sys.exc_info()
或规范化异常。它清除错误指示器并直接为您提供错误指示器的原始内容。
显式异常链接 (raise Something() from existing_exception
) 通过 PyException_SetCause
将新异常的 __cause__
设置为现有异常。这两个异常都需要异常对象,所以如果你想从 C 中做同样的事情,你必须规范化异常并自己调用 PyException_SetCause
。
隐式异常链接(raise Something()
在 except
块中)通过 PyException_SetContext
将新异常的 __context__
设置为现有异常。类似于PyException_SetCause
,这需要异常对象和异常规范化。 except
块中的 raise Something() from existing_exception
实际上设置了 __cause__
和 __context__
,如果你想在 C 级别执行显式异常链接,你通常应该这样做。
- 据我所知,技术上没有必要,但无论如何这样做可能是个好主意。看起来
PyErr_Format
和其他设置错误指示器的函数将首先清除错误指示器(如果已经设置),但大多数函数都没有记录。 - 有点,但这可能是个坏主意。您可以规范化错误指示器并设置异常对象的
message
属性,但这不会影响args
或异常 class 可能对其参数所做的任何其他事情,这可能会导致奇怪的问题。或者,您可以使用PyErr_Fetch
获取错误指示器并使用PyErr_Restore
的值的新字符串恢复它,但是如果有一个现有的异常对象,它会丢弃它,并且它会做出关于异常 class 的签名。 是的,这是可能的,但是通过 public C API 函数来完成它是相当笨拙和手动的。您必须手动执行大量规范化、取消引发和引发异常。
There are efforts to make C-level exception chaining more convenient, but so far, the more convenient functions are all considered internal. For example,
_PyErr_FormatFromCause
类似于PyErr_Format
,但它将新异常链接到现有的传播异常(通过__context__
和__cause__
.我暂时不建议直接调用它;它是非常新的(3.6+),而且很可能会发生变化(具体来说,如果看到它在新的 Python 版本中失去其前导下划线,我不会感到惊讶)。相反,复制 implementation of
_PyErr_FormatFromCause
/_PyErr_FormatVFromCause
(and respecting the license) 是确保您拥有正确的规范化和链接的繁琐位的好方法。如果您想在 C 级别执行隐式(仅
__context__
)异常链接,它也是一个有用的工作参考 - 只需删除处理__cause__
.[=56= 的部分]