代码之家  ›  专栏  ›  技术社区  ›  Davis Herring

谁应该打电话给皮尔?

  •  3
  • Davis Herring  · 技术社区  · 7 年前

    如果可能设置了错误指示符,那么用于Python的capi中的许多函数都不安全。特别地, PyFloat_AsDouble 模棱两可的 PyErr_Occurred 如果已经设置了错误指示灯,他们会认为他们失败了(请注意,这或多或少保证会发生在 PyIter_Next )更一般地说,任何可能失败的功能都会覆盖错误指示器,如果它失败了,这可能是可取的,也可能是不可取的。

    Py_DECREF 可以执行任意代码的局部变量和(除非它可能(间接)释放的所有对象的类型已知)(这是一个很好的例子,说明了清除代码有可能失败。)解释器捕获异常 提高 在这样的毁灭者中 进入之内 他们 .

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

    Py\u减量 错误处理路径上的。而防御性编程的原则则建议在 二者都 在某些地方,更好的代码来自(仔细编程)一个 普遍的 约定(覆盖正在执行的任意代码)。

    errno 必须由任意代码的调用者保存,即使(像Python析构函数中被抑制的异常)不希望设置该代码 厄尔诺 任何事。主要原因是,它可以重置(但永远不会为0)由许多人 成功的 厄尔诺 报告先前存在的错误:C程序员必须设置 厄尔诺 另一个原因是,在大多数C程序中,调用一些没有错误报告的任意代码并不是一种常见的操作,因此为它增加其他代码的负担是毫无意义的。

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

    1 回复  |  直到 6 年前
        1
  •  4
  •   user2357112    7 年前

    Py_DECREF ,你不需要打电话 PyErr_Fetch . Py\u减量 Py\u减量 ,您可能需要自己处理事情。)

    例如, tp_finalize ,对象销毁的步骤之一,最有可能调用任意Python代码, is explicitly responsible for saving and restoring an active exception :

    tp\U最终确定 因此,编写非平凡终结器的建议方法是:

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

    为了 __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\u减量 虽然设置了一个异常是很可怕的,而且您正在考虑它是很好的,但是只要对象销毁代码的行为正常,它应该是正常的。


    皮耶尔 PyErr_Restore 完成后的异常状态。如果在清理过程中出现了另一个异常,可以将其链接起来( awkward but possible 在C级),或用 PyErr_WriteUnraisable PyErr_Clear -或者是通过 PyErr\u还原 -将原始异常状态覆盖到它上面。