如果可能设置了错误指示符,则C API for Python中的许多函数都不安全使用。特别是PyFloat_AsDouble
和类似的功能含糊的,因为它们没有保留用于指示错误的返回值:如果它们成功(但碰巧返回用于错误的值),则客户端如果仅设置了错误指示符,则调用PyErr_Occurred
的调用将认为它们已失败。 (请注意,PyIter_Next
或多或少会发生这种情况。)更一般而言,任何可能失败的函数都会覆盖错误指示符(如果有的话),这可能是不希望的。
不幸的是,使用错误指示符集调用此类函数的可能性一点也不不可能:对错误的常见反应是对Py_DECREF
局部变量,和(除非所有对象的类型可能是(间接地)释放,可以执行任意代码。 (这是清除代码可能导致失败的危险的一个很好的例子。)解释器在此类析构函数中捕获了 raised 引发的异常,但并不能防止异常泄漏进入他们。
在两端,我们可以使用PyErr_Fetch
和PyErr_Restore
来防止这些问题。围绕一个模糊函数的调用,它们可以可靠地确定它是否成功;放在Py_DECREF
周围,它们可以防止在执行任何敏感代码时首先设置错误指示符。 (它们甚至还可以在可能会失败的直接调用的清理代码周围使用,以便允许选择传播哪个异常。在这种情况下,将其放置在哪里毫无疑问:清理代码无论如何都无法在多个异常之间进行选择)
任何一种放置方式都显着增加了代码复杂度和执行时间:对歧义函数的调用很多,并且在错误处理路径上有很多Py_DECREF
。虽然防御性编程的原理建议在两个地方都使用它,但通用约定(仔细编程)会产生更好的代码(以覆盖正在执行的任意代码) )。
C本身具有这样的约定:errno
必须由任意代码的调用者保存,即使(例如Python析构函数中受抑制的异常)该代码不应期望将errno
设置为任何值。主要原因是可以通过许多成功库调用(使其不能在内部处理错误)将其重置(但永远不能重置为0),从而进一步缩小了{{1} }具有一些重要的价值。 (这也防止了errno
报告先前存在的错误时出现的问题:C程序员必须在调用歧义函数之前将PyErr_Occurred
设置为0。)另一个原因是“无误地调用任意代码报告”在大多数C程序中都不是常见的操作,因此为此负担其他代码将是荒谬的。
是否存在这样的约定(即使CPython本身中没有遵循它的错误代码)?失败的话,是否有技术上的理由来指导一个人的选择?也许这是一个工程上的问题,它是基于过于随意的“任意”阅读:CPython是否应该在处理析构函数异常时保存并恢复错误指示符本身?
答案 0 :(得分:3)
如果您的清理工作只是一堆Py_DECREF
,则无需致电PyErr_Fetch
。 Py_DECREF
旨在安全地调用带有异常集的电话。如果Py_DECREF
中的代码需要执行对异常集不安全的操作,它将负责保存和恢复异常状态。 (如果清理不仅仅涉及Py_DECREF
,您可能需要自己处理。)
例如,tp_finalize
是对象破坏步骤之一,最有可能调用任意Python代码is explicitly responsible for saving and restoring an active exception:
tp_finalize
不应更改当前的异常状态; 因此,推荐的写平凡终结器的方法是:static void local_finalize(PyObject *self) { PyObject *error_type, *error_value, *error_traceback; /* Save the current exception, if any. */ PyErr_Fetch(&error_type, &error_value, &error_traceback); /* ... */ /* Restore the saved exception. */ PyErr_Restore(error_type, error_value, error_traceback); }
对于用Python编写的__del__
方法,您可以在slot_tp_finalize
中查看相关处理:
/* Save the current exception, if any. */
PyErr_Fetch(&error_type, &error_value, &error_traceback);
/* Execute __del__ method, if any. */
del = lookup_maybe_method(self, &PyId___del__, &unbound);
if (del != NULL) {
res = call_unbound_noarg(unbound, del, self);
if (res == NULL)
PyErr_WriteUnraisable(del);
else
Py_DECREF(res);
Py_DECREF(del);
}
/* Restore the saved exception. */
PyErr_Restore(error_type, error_value, error_traceback);
弱引用系统还takes responsibility用于在调用弱引用回调之前保存异常状态:
if (*list != NULL) {
PyWeakReference *current = *list;
Py_ssize_t count = _PyWeakref_GetWeakrefCount(current);
PyObject *err_type, *err_value, *err_tb;
PyErr_Fetch(&err_type, &err_value, &err_tb);
if (count == 1) {
PyObject *callback = current->wr_callback;
current->wr_callback = NULL;
clear_weakref(current);
if (callback != NULL) {
if (((PyObject *)current)->ob_refcnt > 0)
handle_callback(current, callback);
Py_DECREF(callback);
}
}
else {
...
因此,在设置了异常的情况下调用Py_DECREF
是令人恐惧的,考虑到这样做是件好事,但是只要对象销毁代码的行为正常,就可以了。
那么,除了清除引用之外,您还需要做更多的清理工作吗?在这种情况下,如果清理不安全且无法处理异常集,则可能应在完成后调用PyErr_Fetch
和PyErr_Restore
异常状态。如果在清理过程中引发了其他异常,则可以将其链接(在C级上awkward but possible),或者使用PyErr_WriteUnraisable
将简短警告转储到stderr,然后通过{{ 1}}-或通过PyErr_Clear
-对其上的原始异常状态进行