我有一些代码试图将对象解析为整数:
long val = PyLong_AsLong(obj);
if(val == -1 && PyErr_Occurred()) {
return -1;
}
这里obj
是普通的PyObject *
,如果TypeError
不是整数,则PyLong_AsLong
会产生非常通用的obj
。
我想将错误消息转换为更具信息性的内容,因此我想修改现有的错误对象或重新提出它。
我当前的解决方案是这样做:
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
,尽管我的直觉告诉我这可能与某种情况有关。
答案 0 :(得分:1)
您现有的代码很好,但是如果您想做与异常链接等效的操作,则可以。如果要跳过此操作,请跳到答案结尾附近的第3点。
要说明如何处理诸如修改传播的异常或执行与raise Something() from existing_exception
等效的操作,首先,我们必须说明异常状态在C级别如何工作。
传播异常由{<3}}表示,该per-thread error indicator由类型,值和 traceback 组成。听起来很像sys.exc_info()
,但事实并非如此。 sys.exc_info()
适用于Python级代码捕获的异常,而不适用于仍在传播的异常。
错误指示符可能是 unnormalized ,这基本上意味着尚未执行构造异常对象的工作,并且错误指示符中的 value 是'是例外 type 的实例。存在这种状态是为了提高效率。如果在规范化之前通过PyErr_Clear
清除了错误指示符,Python将跳过很多引发异常的工作。异常归一化由PyErr_NormalizeException
执行,在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
类似,这需要异常对象和异常规范化。实际上,raise Something() from existing_exception
块内的except
设置了__cause__
和__context__
,并且,如果要在C级执行显式异常链接,通常应该这样做。>
PyErr_Format
和其他设置错误指示符的函数会先清除错误指示符(如果已设置),但是大多数情况下没有对此进行记录。message
属性,但这不会影响args
或异常类对其参数进行的其他处理,这可能会导致奇怪的问题。另外,您可以使用PyErr_Fetch
来获取错误指示符,并使用PyErr_Restore
的值用新字符串来恢复该错误指示符,但是如果存在的话,这将丢弃现有的异常对象,并且该对象将对异常类的签名。是的,这是可能的,但是通过公共C API函数进行操作是很尴尬和手动的。您必须手动进行大量标准化,取消处理和引发异常。
There是efforts,以使C级异常链接更加方便,但是到目前为止,所有更方便的功能都被认为是内部功能。例如,_PyErr_FormatFromCause
与PyErr_Format
类似,但是它将新的异常链接到现有的传播异常中(通过__context__
和__cause__
。
我不建议现在直接调用它;它是一个非常新的版本(3.6+),并且很有可能会发生变化(特别是,我不奇怪看到它在新的Python版本中失去了其领先的下划线)。相反,复制_PyErr_FormatFromCause
/ _PyErr_FormatVFromCause
的{{3}}(并尊重implementation)是确保您具有一定程度的规范化和链接权的好方法。 / p>
如果您想在C级执行隐式(仅__context__
)异常链接,它也是一个有用的参考,只需删除处理__cause__
的部分即可。