在C API中修改或重新引发Python错误

时间:2018-06-25 19:30:53

标签: python python-3.x exception-handling python-c-api

我有一些代码试图将对象解析为整数:

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;
}

这是重新引发错误的正确方法吗?具体来说,

  1. 我是否需要打PyErr_Clear?我怀疑它正确地拒绝了现有的异常对象,但是我不确定。
  2. 是否可以在不重新引发该错误的情况下修改已经抛出的错误消息?
  3. 是否可以选择进行raise new_err from old_err的等效操作?

我不确定在这种情况下如何使用PyErr_SetExcInfo,尽管我的直觉告诉我这可能与某种情况有关。

1 个答案:

答案 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_FetchPyErr_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级执行显式异常链接,通常应该这样做。


    据我所知,
  1. 从技术上讲不是必须的,但无论如何可能是一个好主意。看起来PyErr_Format和其他设置错误指示符的函数会先清除错误指示符(如果已设置),但是大多数情况下没有对此进行记录。
  2. 排序,但这可能不是一个好主意。您可以规范化错误指示符并设置异常对象的message属性,但这不会影响args或异常类对其参数进行的其他处理,这可能会导致奇怪的问题。另外,您可以使用PyErr_Fetch来获取错误指示符,并使用PyErr_Restore的值用新字符串来恢复该错误指示符,但是如果存在的话,这将丢弃现有的异常对象,并且该对象将对异常类的签名。
  3. 是的,这是可能的,但是通过公共C API函数进行操作是很尴尬和手动的。您必须手动进行大量标准化,取消处理和引发异常。

    Thereefforts,以使C级异常链接更加方便,但是到目前为止,所有更方便的功能都被认为是内部功能。例如,_PyErr_FormatFromCausePyErr_Format类似,但是它将新的异常链接到现有的传播异常中(通过__context____cause__

    我不建议现在直接调用它;它是一个非常新的版本(3.6+),并且很有可能会发生变化(特别是,我不奇怪看到它在新的Python版本中失去了其领先的下划线)。相反,复制_PyErr_FormatFromCause / _PyErr_FormatVFromCause的{​​{3}}(并尊重implementation)是确保您具有一定程度的规范化和链接权的好方法。 / p>

    如果您想在C级执行隐式(仅__context__)异常链接,它也是一个有用的参考,只需删除处理__cause__的部分即可。