从 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"
something
和 something_else
都引用了相同的内存(包含文本 "hello" 的字符串)。删除一个不影响另一个。尽管您的代码使用了 C 函数,但基本原理是相同的。尝试用两个版本的 C 函数打印出引用计数,这样会更清楚。
您不想做的是在return创建对象之前调用Py_DECREF
。在那种情况下,引用计数可以降为零,returned 的东西是完全无效的。
这个问题很简单,但有助于巩固我的理解。我知道 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"
something
和 something_else
都引用了相同的内存(包含文本 "hello" 的字符串)。删除一个不影响另一个。尽管您的代码使用了 C 函数,但基本原理是相同的。尝试用两个版本的 C 函数打印出引用计数,这样会更清楚。
您不想做的是在return创建对象之前调用Py_DECREF
。在那种情况下,引用计数可以降为零,returned 的东西是完全无效的。