Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/332.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 谁应该打电话给皮耶尔?_Python_Error Handling_Cpython_Python Internals - Fatal编程技术网

Python 谁应该打电话给皮耶尔?

Python 谁应该打电话给皮耶尔?,python,error-handling,cpython,python-internals,Python,Error Handling,Cpython,Python Internals,如果可能设置了错误指示符,那么用于Python的C API中的许多函数都不安全。特别是,PyFloat\u AsDouble和类似的函数是不明确的,因为它们没有保留用于指示错误的返回值:如果它们成功(但恰好返回用于错误的值),如果已经设置了错误指示器,则调用PyErr\u occurrent的客户端将认为它们失败了。(请注意,PyIter\u Next或多或少保证会发生这种情况)一般来说,任何可能失败的函数都会覆盖错误指示器,如果失败,这可能是可取的,也可能是不可取的 不幸的是,使用错误指示符集

如果可能设置了错误指示符,那么用于Python的C API中的许多函数都不安全。特别是,
PyFloat\u AsDouble
和类似的函数是不明确的,因为它们没有保留用于指示错误的返回值:如果它们成功(但恰好返回用于错误的值),如果已经设置了错误指示器,则调用
PyErr\u occurrent
的客户端将认为它们失败了。(请注意,
PyIter\u Next
或多或少保证会发生这种情况)一般来说,任何可能失败的函数都会覆盖错误指示器,如果失败,这可能是可取的,也可能是不可取的

不幸的是,使用错误指示符集调用此类函数的可能性并非完全不可能:对错误的常见反应是使用
Py_DECREF
局部变量,以及(除非已知可能(间接)由它释放的所有对象的类型)可以执行任意代码。(这是一个很好的例子,说明了清除代码有可能失败。)解释器捕获在此类析构函数中引发的异常,但它不能防止异常泄漏到它们中

任一端,我们可以使用
PyErr\u Fetch
PyErr\u Restore
来防止这些问题。围绕对一个不明确函数的调用,它们允许可靠地确定它是否成功;在
Py_DECREF
周围放置,它们首先防止在执行任何易受影响的代码时设置错误指示器。(它们甚至可以用于可能失败的直接调用清理代码,以便选择要传播的异常。在这种情况下,将其放置在何处是毫无疑问的:清理代码无论如何不能在多个异常之间进行选择。)

任何一种位置的选择都会显著增加代码复杂性和执行时间:有很多对不明确函数的调用,并且在错误处理路径上有很多
Py_DECREF
s。虽然防御性编程的原则会建议在这两个地方都使用它,但是(小心地编程)一个通用的约定(覆盖正在执行的任意代码)会产生更好的代码

C本身有这样一个约定:
errno
必须由任意代码的调用者保存,即使(像Python析构函数中被抑制的异常)该代码不需要设置
errno
。主要原因是它可以通过许多成功的库调用(让它们在内部处理错误)重置(但决不能重置为0),从而进一步缩小可安全执行的操作集,而
errno
具有一些重要的值。(这也防止了当出现
pyerru
报告先前存在的错误时出现的问题:C程序员在调用不明确的函数之前必须将
errno
设置为0。)另一个原因是“调用一些没有错误报告的任意代码”不是大多数C程序中的常见操作,因此,为了它而加重其他代码的负担将是荒谬的


是否有这样的约定(即使CPython本身中有不遵循它的错误代码)?如果做不到这一点,是否有技术上的理由来指导选择建立一个?或者,这可能是一个基于“任意”的过于文字化的工程问题:CPython在处理析构函数异常时是否应该保存并恢复错误指示器本身?

如果您的清理只是一堆
Py\u DECREF
,您不应该需要调用
PyErr\u Fetch
Py_DECREF
旨在安全地调用异常集。如果
Py_DECREF
中的代码需要对异常集执行不安全的操作,它将负责保存和恢复异常状态。(如果您的清理不仅仅涉及
Py_DECREF
,您可能需要自己处理事情。)

例如,
tp_finalize
,最有可能调用任意Python代码的对象销毁步骤之一:

tpu 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编写的
\uuu del\uu
方法,您可以在中看到相关的处理:

弱引用系统还用于在调用弱引用回调之前保存异常状态:

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\u Fetch
,并
PyErr\u Restore
异常状态。如果在清理过程中出现另一个异常,您可以将其链接(在C级别),或者向stderr转储一个简短的警告,然后通过
PyErr\u Clear
-ing或
PyErr\u Restore
-ing原始异常状态来抑制新异常。

Hm-我知道我看到一个异常仍然通过
Py\u DECREF
设置,但那是3年前的事了,显然是某些C类型中的一个bug(而不是
\uu del\uu
weakref.finalize
)。如果我知道在这种情况下的一般谨慎程度,我会知道在我有机会的时候识别出哪种C类型的行为不正常!你的回答暗示了,但并没有说任何处理错误的例程都有责任,而不是通过
Py_DECREF
保存/隐藏,
pyerru WriteUnraisable
如有必要,删除和还原(或链)异常。为完整起见,您可以添加它吗?@davishering:Answer exp
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 {
        ...