【问题标题】:Modifying or Reraising Python error in C API在 C API 中修改或重新引发 Python 错误
【发布时间】:2018-12-04 10:52:49
【问题描述】:

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

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

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

  1. 我需要打电话给PyErr_Clear吗?我怀疑它正确地定义了现有的异常对象,但我不确定。
  2. 我可以修改当时已经抛出的错误消息而不重新引发它吗?
  3. 是否有与raise new_err from old_err 等效的选项?

我不知道如何在这种情况下使用PyErr_SetExcInfo,尽管我的直觉告诉我它可能以某种方式相关。

【问题讨论】:

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


    【解决方案1】:

    您现有的代码很好,但如果您想做与异常链接等效的操作,您可以。如果您想跳到如何做,请跳到答案末尾附近的第 3 点。


    为了解释如何修改传播的异常或执行raise Something() from existing_exception 的等效操作,首先,我们必须解释异常状态在 C 级别是如何工作的。

    传播异常由per-thread error indicator 表示,该per-thread error indicatortypevaluetraceback 组成。这听起来很像sys.exc_info(),但不一样。 sys.exc_info() 适用于 Python 级代码捕获的异常,而不是仍在传播的异常。

    错误指示可能是unnormalized,基本表示构造异常对象的工作还没有进行,错误指示中的是' t 异常type 的实例。这种状态是为了效率而存在的;如果在需要规范化之前PyErr_Clear 清除了错误指示符,Python 将跳过引发异常的大部分工作。异常规范化由PyErr_NormalizeException 执行,在PyException_SetTraceback 中进行了一些额外的工作来设置异常对象的__traceback__ 属性。

    PyErr_Clear 有点像 C 中的 except 块,但它只是清除错误指示器,而不让您检查大部分异常信息。要捕获异常并检查它,您需要PyErr_FetchPyErr_Fetch 就像捕获异常并检查 sys.exc_info(),但它没有设置 sys.exc_info() 或规范化异常。它会清除错误指示器并直接为您提供错误指示器的原始内容。

    显式异常链接 (raise Something() from existing_exception) 通过通过 PyException_SetCause 将新异常的 __cause__ 设置为现有异常来工作。这需要两个异常的异常对象,所以如果你想在 C 中做同样的事情,你必须规范化异常并自己调用PyException_SetCause

    隐式异常链接(except 块中的raise Something())通过通过PyException_SetContext 将新异常的__context__ 设置为现有异常来工作。类似于PyException_SetCause,这需要异常对象和异常规范化。 raise Something() from existing_exceptionexcept 块中实际上同时设置了__cause____context__,如果您想在C 级别执行显式异常链接,通常应该这样做。


    1. 据我所知,技术上没有必要,但无论如何这样做可能是个好主意。看起来PyErr_Format 和其他设置错误指示符的函数将首先清除已设置的错误指示符,但其中大多数没有记录。
    2. 有点意思,但这可能是个坏主意。您可以规范化错误指示器并设置异常对象的message 属性,但这不会影响args 或异常类可能对其参数所做的任何其他事情,这可能会导致奇怪的问题。或者,您可以使用PyErr_Fetch 获取错误指示符并使用PyErr_Restore 值的新字符串恢复它,但是如果有一个现有异常对象,它将丢弃现有异常对象,并且它对异常类的签名做出假设.
    3. 是的,这是可能的,但是通过公共 C API 函数执行此操作非常笨拙且需要手动操作。您必须手动执行大量规范化、取消引发和引发异常。

      Thereefforts 以使 C 级异常链接更方便,但到目前为止,更方便的函数都被认为是内部的。例如,_PyErr_FormatFromCause 类似于 PyErr_Format,但它将新异常与现有的、正在传播的异常链接起来(通过 __context____cause__

      我暂时不建议直接调用它;它是非常新的(3.6+),而且很可能会改变(特别是,如果看到它在新的 Python 版本中失去其前导下划线,我不会感到惊讶)。相反,复制_PyErr_FormatFromCause/_PyErr_FormatVFromCauseimplementation(并尊重license)是确保您拥有正确的规范化和链接的好方法。

      如果您想在 C 级别执行隐式(__context__-only)异常链接,这也是一个有用的工作参考 - 只需删除处理 __cause__ 的部分。

    【讨论】:

      猜你喜欢
      • 2020-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-21
      • 1970-01-01
      • 2021-10-04
      相关资源
      最近更新 更多