从 Python C 扩展函数返回参数时需要 INCREF?

INCREF needed when returning argument from Python C Extension function?

这个问题很简单,但有助于巩固我的理解。我知道 C 扩展函数的参数在 C 代码期间保证是实时引用(除非手动 DECREFed)。但是,如果我有一个 C 扩展代码 returns 一个 PyObject* 出现在它的参数列表中,我是否需要在返回它之前增加参数?即以下两项正确的是:

static PyObject return_item(PyObject *self, PyObject *item)
{
    // manipulate item
    return item;
}

static PyObject return_item(PyObject *self, PyObject *item)
{
    // manipulate item
    Py_INCREF(item);
    return item;
}

基于 https://docs.python.org/3/extending/extending.html#ownership-rules,即

The object reference returned from a C function that is called from Python must be an owned reference — ownership is transferred from the function to its caller.

Returning objects to Python from C 我认为是后者(INCREFing 是可行的方法)但我想确定一下。

如果有人从 Python 调用 return_item 函数,他们可能会这样做:

something = Something()
something_else = return_item(something)
del something

如果 return_item 不是 return 传入的参数,而是其他东西,你会期望此时传入的 something 应该被释放内存,因为它的引用计数降为零。

如果您 Py_INCREF 和 return 不是同一个对象,这种情况仍然会发生 - 对象的引用计数将降至 0,您将在 something_else.

TL;DR:是的,您应该 Py_INCREF,因为您通过从函数 returning 创建了对该对象的另一个引用。

不想在return之前增加对象的引用计数。这样做会造成内存泄漏,从而阻止对象被垃圾回收。

将引用计数递增视为 "I am using this memory. Please don't free it." 当您输入 C 代码时,您 "borrow" 来自 Python 的引用,但当您退出 C 代码时,您'对象已完成,不再需要引用。

Python 中的变量和底层内存是分开的,这使得它在内存方面具有一定的效率(read 更多)。另一个答案忽略了这样一个事实,即分配给 something_else 会增加底层内存的引用计数。您可以使用 sys.getrefcount.

自行验证
import sys
something = "hello"
print(sys.getrefcount(something))       # 2 (getrefcount uses a reference)

something_else = something
print(sys.getrefcount(something_else))  # 3 (same memory as something)

del something
print(sys.getrefcount(something_else))  # 2
print(something_else)                   # "hello"

somethingsomething_else 都引用了相同的内存(包含文本 "hello" 的字符串)。删除一个不影响另一个。尽管您的代码使用了 C 函数,但基本原理是相同的。尝试用两个版本的 C 函数打印出引用计数,这样会更清楚。

不想做的是在return创建对象之前调用Py_DECREF。在那种情况下,引用计数可以降为零,returned 的东西是完全无效的。