Python C Extension 需要 Py_INCREF 借来的参考资料,如果不归还给 Python Land?

Python C Extension Need to Py_INCREF a Borrowed Reference if not returning it to Python Land?

如果你获得了借用的参考资料但没有return它到Python土地,你是否应该在获得它时做一个Py_INCREF然后一个Py_DECREF一旦你完成了它,还是这是多余的?

例如,这个简单的例子从 PyDict_GetItem 那里得到一个借用的引用,用借用的引用做一些事情,然后 returns None 到 Python land .在这里使用它时是否需要 Py_INCREF/Py_DECREF 借用的参考资料?

static PyObject PyFoo_bar(PyFoo *self, int field)
{
    int field = 0;

    PyFoo *child = NULL;

    if (!PyArg_ParseTuple(args, "i", &field) {
         return NULL;
    }

    child = PyDict_GetItem(self->children, field);

    // Long code omitted that works on child
    // Is it safe to do so without a Py_INCREF on child now followed by PY_DECREF later?

    // NOT returning child here - just return None
    Py_RETURN_NONE;
}

我担心的是,在调用 PyDict_GetItem 之后,child 的引用计数可能会以某种方式下降到零,然后在我使用它时释放。这可能吗?我不这么认为,因为这里没有删除 GIL,但我不确定。

另一方面,我想知道在此处执行 Py_INCREF/Py_DECREF 是否只是最佳实践。也许稍后我会放弃 GIL,或者稍后决定 return child。 Py_INCREF 很便宜。比如这样是不是更好:

child = PyDict_GetItem(self->children, field);
Py_INCREF(child);
// Long code omitted
Py_DECREF(child);
Py_RETURN_NONE;

这取决于中间"long code"做什么。如果它执行您无法控制的 Python 代码,那么该代码完全有可能访问 self->children dict 并删除 field,此时它的引用计数可以降为零.所以你想防止这种情况并添加 INCREF/DECREF.

请注意,对 child 的任何使用都需要读取类型对象指针,它与引用计数相邻,因此后者将被加载到同一缓存行。对于乱序执行,INCREF/DECREF 基本上是自由操作,因此没有理由将它们排除在外。

我能想到的 执行 INCREF 的最佳原因是 "long code" 有多个退出点(但不执行任意 python代码或触摸 self->children,如上所述)。您必须为每个出口添加 DECREF,丢失一个出口并导致难以调试的内存泄漏的风险很高。