Mad*_*ist 5 python exception python-c-api python-3.x
我有一些代码尝试将对象解析为整数:
long val = PyLong_AsLong(obj);
if(val == -1 && PyErr_Occurred()) {
return -1;
}
Run Code Online (Sandbox Code Playgroud)
这obj是一个普通的PyObject *,并PyLong_AsLong引发一个非常通用的TypeErrorifobj不是整数。
我想将错误消息转换为更具信息性的内容,因此我想修改现有的错误对象,或者重新引发它。
我当前的解决方案是这样做:
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;
}
Run Code Online (Sandbox Code Playgroud)
这是重新引发错误的正确方法吗?具体来说,
PyErr_Clear?我怀疑它正确地减少了现有的异常对象,但我不确定。raise new_err from old_err?我不确定如何PyErr_SetExcInfo在这种情况下使用,尽管我的直觉告诉我它可能在某种程度上相关。
您现有的代码很好,但如果您想要执行与异常链接相同的操作,也可以。如果您想跳到如何执行此操作,请跳至答案末尾附近的第 3 点。
为了解释如何执行诸如修改传播异常或执行等效操作之类的操作raise Something() from existing_exception,首先,我们必须解释异常状态在 C 级别如何工作。
传播异常由每线程错误指示器表示,该指示器由type、value和traceback组成。这听起来很像sys.exc_info(),但又不一样。sys.exc_info()适用于已被 Python 级代码捕获的异常,而不是仍在传播的异常。
错误指示符可能是非规范化的,这基本上意味着构造异常对象的工作尚未执行,并且错误指示符中的值不是异常类型的实例。这种状态的存在是为了效率;如果在需要规范化之前错误指示器被清除PyErr_Clear,Python 就可以跳过引发异常的大部分工作。异常规范化由 执行PyErr_NormalizeException,需要进行一些额外的工作PyException_SetTraceback来设置异常对象的__traceback__属性。
PyErr_Clear有点像 C 语言中的块except,但它只是清除错误指示器,而不让您检查大量异常信息。要捕获异常并检查它,您需要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,这需要异常对象和异常规范化。raise Something() from existing_exception在except块内部实际上设置了__cause__和__context__,如果您想在 C 级别执行显式异常链接,通常应该这样做。
PyErr_Format设置错误指示器的其他函数将首先清除错误指示器(如果已设置),但这对于大多数函数都没有记录。message属性,但这不会影响args异常类可能对其参数执行的任何其他操作,这可能会导致奇怪的问题。或者,您可以使用 获取错误指示符,并PyErr_Fetch使用值的新字符串恢复它PyErr_Restore,但这将丢弃现有的异常对象(如果存在),并且它会对异常类的签名做出假设。是的,这是可能的,但是通过公共 C API 函数来实现这一点非常尴尬并且需要手动操作。您必须手动执行大量规范化、取消引发和引发异常。
人们正在努力使C 级异常链接更加方便,但到目前为止,更方便的功能都被认为是内部的。例如,_PyErr_FormatFromCause类似于PyErr_Format,但它将新异常与现有的传播异常(通过 和 )链接__context__起来__cause__。
我现在不建议直接调用它;它是非常新的(3.6+),并且很可能会改变(具体来说,我不会惊讶地发现它在新的 Python 版本中失去了前导下划线)。相反,复制/的实现(并尊重许可证)是确保您拥有规范化和链接权利的繁琐部分的好方法。_PyErr_FormatFromCause_PyErr_FormatVFromCause
如果您想在 C 级别执行隐式(__context__仅)异常链接,它也是一个有用的参考 - 只需删除处理__cause__.