在C API中修改或重新引发Python错误 [英] Modifying or Reraising Python error in C API

查看:89
本文介绍了在C API中修改或重新引发Python错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a bit of code that tries to parse an object as an integer:

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

此处 obj 是香草 PyObject * PyLong_AsLong 会引起非常普通的 TypeError 如果 obj 不是整数。

Here obj is a vanilla PyObject *, and PyLong_AsLong raises a very generic TypeError if obj is not an integer.

我想将错误消息转换为更有意义的内容,所以我想修改现有的错误对象或重新提出。

I would like to transform the error message into something a bit more informative, so I would like to either modify the existing error object, or to reraise it.

我当前的解决方案是:

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

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

Is this the proper way to reraise an error? Specifically,


  1. 我是否需要致电 PyErr_Clear 完全吗?我怀疑它正确地拒绝了现有的异常对象,但我不确定。

  2. 是否可以修改此时已引发的错误消息,而无需重新引发它?

  3. 是否可以选择等效于从old_err 提升 new_err?

  1. Do I need to call PyErr_Clear at all? I suspect that it properly decrefs the existing exception object, but I'm not sure.
  2. Can I modify the message of the error that has already been thrown at that point without re-raising it?
  3. Is there an option to do the equivalent of raise new_err from old_err?

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

I am not sure how to use PyErr_SetExcInfo for this situation, although my gut tells me it may be relevant somehow.

推荐答案

您现有的代码很好,但是如果您要进行等效的异常链接,则可以。如果要跳过此操作,请跳到答案末尾的第3点。

Your existing code is fine, but if you want to do the equivalent of exception chaining, you can. If you want to skip to how to do that, jump to point 3 near the end of the answer.

要说明如何做诸如修改传播的异常或执行与现存的异常 raise Something()等效的操作,首先,我们必须解释异常状态在C级别如何工作。

To explain how to do things like modify a propagating exception or perform the equivalent of raise Something() from existing_exception, first, we'll have to explain how exception state works at C level.

传播异常由每线程错误指示器 ,由类型 traceback组成。听起来很像 sys.exc_info() ,但是不一样。 sys.exc_info()适用于被Python级代码捕获的异常,而不是仍在传播的异常。

A propagating exception is represented by a per-thread error indicator consisting of a type, value, and traceback. That sounds a lot like sys.exc_info(), but it's not the same. sys.exc_info() is for exceptions that have been caught by Python-level code, not exceptions that are still propagating.

错误指示符可能是 unnormalized ,这基本上意味着尚未执行构造异常对象的工作,并且 value 不是异常 type 的实例。存在这种状态是为了提高效率。如果在需要规范化之前通过 PyErr_Clear 清除了错误指示符,Python可以跳过很多引发异常的工作。异常规范化由 PyErr_NormalizeException ,其中 PyException_SetTraceback 设置例外对象的 __ traceback __ 属性。

The error indicator may be unnormalized, which basically means that the work of constructing an exception object hasn't been performed, and the value in the error indicator isn't an instance of the exception type. This state exists for efficiency; if the error indicator is cleared by PyErr_Clear before normalization is needed, Python gets to skip much of the work of raising an exception. Exception normalization is performed by PyErr_NormalizeException, with a bit of extra work in PyException_SetTraceback to set the exception object's __traceback__ attribute.

PyErr_Clear 类似于 except 块的C等效项,但它只是清除了错误指示器,而不会让您检查许多异常信息。要捕获异常并进行检查,您需要 PyErr_Fetch PyErr_Fetch 就像捕获异常并检查 sys.exc_info()一样,但是它没有设置 sys.exc_info()或规范化异常。它将清除错误指示符并直接为您提供错误指示符的原始内容。

PyErr_Clear is sort of like the C equivalent of an except block, but it just clears the error indicator, without letting you inspect much of the exception information. To catch an exception and inspect it, you'd want PyErr_Fetch. PyErr_Fetch is like catching an exception and examining sys.exc_info(), but it doesn't set sys.exc_info() or normalize the exception. It clears the error indicator and gives you the raw contents of the error indicator directly.

显式异常链接(从exist_exception中引发Something())可以通过 PyException_SetCause 将新异常的 __ cause __ 设置为现有异常。这两个异常都需要异常对象,因此,如果要从C执行等效操作,则必须规范化异常并自己调用 PyException_SetCause

Explicit exception chaining (raise Something() from existing_exception) works by going through PyException_SetCause to set the new exception's __cause__ to the existing exception. This requires exception objects for both exceptions, so if you want to do the equivalent from C, you'll have to normalize the exceptions and call PyException_SetCause yourself.

隐式异常链接(中的 raise Something()除外块)有效通过 PyException_SetContext 将新异常的 __ context __ 设置为现有异常。与 PyException_SetCause 相似,这需要异常对象和异常规范化。 except 块内的existing_exception 筹集一些东西实际上设置了两个 __ cause __ __ context __ ,并且如果要在C级别执行显式异常链接,通常应该这样做。

Implicit exception chaining (raise Something() in an except block) works by going through PyException_SetContext to set the new exception's __context__ to the existing exception. Similar to PyException_SetCause, this requires exception objects and exception normalization. raise Something() from existing_exception inside an except block actually sets both __cause__ and __context__, and if you want to perform explicit exception chaining at C level, you should usually do the same.


  1. 从技术上讲,这不是必需的,但是无论如何这可能是个好主意。看起来 PyErr_Format 和其他设置错误指示符的函数会先清除错误指示符(如果已设置),但是大多数情况下没有对此进行记录。

  2. 排序,但这可能不是一个好主意。您可以标准化错误指示符并设置异常对象的 message 属性,但这不会影响 args 或其他任何东西异常类可能会使用其参数,这可能会导致奇怪的问题。或者,您可以使用 PyErr_Fetch 获取错误指示符,并使用 PyErr_Restore 的值将其替换为新字符串。

  3. 是的,可以,但是可以通过公共C API函数来实现。非常尴尬和手动。您必须手动进行大量标准化,取消处理和引发异常。

  1. Technically not necessary, as far as I can tell, but it's probably a good idea to do it anyway. It looks like PyErr_Format and other functions that set the error indicator will clear the error indicator first if it's already set, but this isn't documented for most of them.
  2. Sort of, but it's probably a bad idea. You can normalize the error indicator and set the exception object's message attribute, but this won't affect args or anything else the exception class might do with its arguments, and that could lead to weird problems. Alternatively, you could fetch the error indicator with PyErr_Fetch and restore it with a new string for the value with PyErr_Restore, but that will throw away an existing exception object if there is one, and it makes assumptions about the exception class's signature.
  3. Yeah, that's possible, but doing it through public C API functions is pretty awkward and manual. You'd have to manually do a lot of normalization, unraising, and raising exceptions.

努力使C级异常链更多方便,但到目前为止,所有更方便的功能都被视为内部功能。例如, _PyErr_FormatFromCause 类似于 PyErr_Format ,但是它将新的异常与现有的传播异常(通过两个 __ context __ __原因__

There are efforts to make C-level exception chaining more convenient, but so far, the more convenient functions are all considered internal. For example, _PyErr_FormatFromCause is like PyErr_Format, but it chains the new exception off of an existing, propagating exception (through both __context__ and __cause__.

我现在不建议直接调用它;这非常新版(3.6+),并且很有可能会发生变化(特别是,我不会惊讶地看到它在新的Python版本中失去了领先的下划线),而是复制实现 _PyErr_FormatFromCause / _PyErr_FormatVFromCause (并遵守许可证)是确保您正确地进行规范化和链接的好方法。

I wouldn't recommend calling it directly for now; it's very new (3.6+), and it's very likely to change (specifically, I would be unsurprised to see it lose its leading underscore in a new Python version). Instead, copying the implementation of _PyErr_FormatFromCause/_PyErr_FormatVFromCause (and respecting the license) is a good way to make sure you have the fiddly bits of normalization and chaining right.

如果要在C级执行隐式(仅 __ context __ )异常链接,从工作有用的参考-只需删除处理 __ cause __

It's also a useful reference to work from if you want to perform implicit (__context__-only) exception chaining at C level - just remove the part that handles __cause__.

这篇关于在C API中修改或重新引发Python错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆