Python 为什么我们几乎总是在减少引用计数之前重新分配对象成员?

Python 为什么我们几乎总是在减少引用计数之前重新分配对象成员?,python,python-c-api,python-extensions,Python,Python C Api,Python Extensions,为了学习如何编写C扩展模块来定义新类型,我一直在通读。在某个时候,给出了以下tp_init实现: static int Custom_init(CustomObject *self, PyObject *args, PyObject *kwds) {     static char *kwlist[] = {"first", "last", "number", NULL};     PyObject *first = NULL, *la

为了学习如何编写C扩展模块来定义新类型,我一直在通读。在某个时候,给出了以下
tp_init
实现:

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }
    return 0;
}
我面临的问题是,我看不出这样分配第一个成员有什么害处:

if (first) {
    Py_XDECREF(self->first);
    Py_INCREF(first);
    self->first = first;
}
另一方面,上述教程警告我们不要使用它,因为:

我们的类型不限制第一个成员的类型,因此它可以是 任何种类的物体。它可能有一个析构函数,使代码 尝试访问第一个成员的已执行

它还指出:

为了偏执并保护自己不受这种可能性的影响,我们 几乎总是在减少引用之前重新分配成员 计数


考虑到这些警告,我仍然看不出这两种方法之间有什么实际的区别。那么,为什么我要使用第一个在减少旧PyObject引用计数之前将成员重新分配给新PyObject的
first
。可能您缺少的细节是
pyxdecref
可能会导致执行任意Python代码,在Python代码完成执行之前,C函数是不会继续的?我相信在垃圾收集器启动并且PyObject的引用计数达到零之前,您引用的任意Python代码都不会执行。我不明白的是,与非推荐方式相比,推荐方式如何使我们能够避免该问题?对象引用计数在任何DECREF上都可以达到0。“垃圾收集器”是一个单独的功能,偶尔运行它来查找循环引用。