代码之家  ›  专栏  ›  技术社区  ›  Mad Physicist

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

  •  1
  • Mad Physicist  · 技术社区  · 6 年前

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

    long val = PyLong_AsLong(obj);
    if(val == -1 && PyErr_Occurred()) {
        return -1;
    }
    

    在这里 obj 是香草的 PyObject * ,和 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 在这种情况下,尽管我的直觉告诉我这可能是相关的。

    1 回复  |  直到 6 年前
        1
  •  2
  •   user2357112    6 年前

    你现有的代码很好,但是如果你想做等价的异常链接,你可以。如果你想跳到怎么做,跳到答案末尾的第3点。


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

    传播异常由 per-thread error indicator 包括 类型 , 价值 回溯 是的。听起来很像 sys.exc_info() 但不一样。 系统执行信息() 是为了那些 抓住了 按python级别的代码,不是仍在传播的异常。

    错误指示器可能是 小精灵 ,这基本上意味着构造异常对象的工作尚未执行,并且 价值 在错误指示器中不是异常的实例 类型 是的。此状态存在是为了提高效率;如果错误指示器被清除 PyErr_Clear 在需要规范化之前,Python会跳过引发异常的大部分工作。异常规范化由 PyErr_NormalizeException ,还有一些额外的工作 PyException_SetTraceback 设置异常对象的 __traceback__ 属性。

    皮埃尔里克 有点像C的等价物 except 块,但它只是清除了错误指示器,而不让您检查很多异常信息。要捕获异常并检查它,您需要 PyErr_Fetch 是的。 皮耶尔 就像捕捉异常和检查 系统执行信息() ,但它没有设置 系统执行信息() 或使异常正常化。它清除错误指示器并直接给出错误指示器的原始内容。

    显式异常链接( 从现有的异常中引发 )通过实践来工作 PyException_SetCause 设置新异常的 __cause__ 对现有的异常。这需要对两个异常都使用异常对象,因此如果要从C中执行等效操作,则必须规范化异常并调用 PyException设置原因 你自己。

    隐式异常链( raise Something() 在一个 除了 (块)通过 PyException_SetContext 设置新异常的 __context__ 到现有异常。类似 PyException设置原因 这需要异常对象和异常规范化。 从现有的异常中引发 在一个 除了 块实际上设置了两个 _原因__ _上下文__ ,如果要在C级别执行显式异常链接,通常也应该这样做。


    1. 就我所知,技术上是不必要的,但不管怎样,这样做可能是个好主意。看起来像 PyErr_Format 设置错误指示器的其他函数将首先清除错误指示器,如果已经设置了错误指示器,但大多数都没有记录。
    2. 有点,但这可能是个坏主意。可以对错误指示符进行归一化,并设置异常对象的 message 属性,但这不会影响 args 或者异常类可能对其参数做的任何其他事情,这可能会导致奇怪的问题。或者,可以使用 皮耶尔 并用一个新字符串将其还原为 PyErr_Restore ,但如果存在一个现有的异常对象,它将丢弃该对象,并对异常类的签名进行假设。
    3. 是的,这是可能的,但是通过公共的C API函数来做它是相当尴尬和手动的。您必须手动执行大量的规范化、反编译,并引发异常。

      There efforts 为了使c级异常链接更加方便,但是到目前为止,更方便的函数都被认为是内部的。例如, _PyErr_FormatFromCause 就像 pyerr_格式 ,但它将新异常与现有的传播异常(通过 _上下文__ _原因__ 是的。

      我不建议现在直接调用它;它是非常新的(3.6+),而且很可能会更改(特别是,如果看到它在新的python版本中丢失了它的前导下划线,我不会感到惊讶)。相反,复制 implementation 属于 _ Pyerr_FormatFromCause公司 / _PyErr_FormatVFromCause (并尊重 license 这是确保你有一个标准化和链右键的好方法。

      如果要执行隐式( _上下文__ -仅)C级异常链接-只需删除处理 _原因__ 是的。