为什么 PySequence_GetItem return 是一个新的引用?
Why does PySequence_GetItem return a new reference?
我被 C/C++ python 扩展中的内存泄漏所困扰,因为我假设 PySequence_GetItem
会 return 一个借用的引用,在与 PyList_GetItem
和 PyTuple_GetItem
相同。我的问题是:为什么 PySequence_GetItem
return 是新引用而 PyList_GetItem
和 PyTuple_GetItem
return 是借来的引用?
来自docs:
PyObject* PySequence_GetItem(PyObject *o, Py_ssize_t i)
Return value: New reference.
Return the ith element of o, or NULL on failure. This is the equivalent of the Python expression o[i].
您从 PySequence
获得新引用,因为这是 PySequence
协议定义的内容。
然而,有充分的理由以这种方式定义协议:并非所有序列都由内存支持(如 list
、tuple
),因为某些项目是动态创建的(像 range
, unicode
).
对于 list
和 tuple
所有项目都归 list
/tuple
所有(它们不是临时对象)所以我们可以借用它们(借用是一个小优化)- list
/tuple
最终会释放内存。
range
是序列的另一个例子。它实现了 PySequence-protocol):
static PySequenceMethods range_as_sequence = {
(lenfunc)range_length, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
(ssizeargfunc)range_item, /* sq_item */
0, /* sq_slice */
0, /* sq_ass_item */
0, /* sq_ass_slice */
(objobjproc)range_contains, /* sq_contains */
};
然而,PySequence_GetItem
返回的对象是临时对象(即没有人在函数外拥有对它的引用),我们可以在range_item
的源代码中验证这一点:
static PyObject *
range_item(rangeobject *r, Py_ssize_t i)
{
PyObject *res, *arg = PyLong_FromSsize_t(i);
if (!arg) {
return NULL;
}
res = compute_range_item(r, arg);
Py_DECREF(arg);
return res;
}
其中 compute_range_item
归结为 compute_item
:
static PyObject *
compute_item(rangeobject *r, PyObject *i)
{
PyObject *incr, *result;
/* PyLong equivalent to:
* return r->start + (i * r->step)
*/
incr = PyNumber_Multiply(i, r->step);
if (!incr)
return NULL;
result = PyNumber_Add(r->start, incr);
Py_DECREF(incr);
return result;
}
没有人拥有返回的对象 result
,因此接收方必须注意减少引用计数。
也许还有其他可能的解决方案(某种已创建项目的缓存),但返回新引用是处理即时创建项目问题的最simple/transparent方法。
我被 C/C++ python 扩展中的内存泄漏所困扰,因为我假设 PySequence_GetItem
会 return 一个借用的引用,在与 PyList_GetItem
和 PyTuple_GetItem
相同。我的问题是:为什么 PySequence_GetItem
return 是新引用而 PyList_GetItem
和 PyTuple_GetItem
return 是借来的引用?
来自docs:
PyObject* PySequence_GetItem(PyObject *o, Py_ssize_t i)
Return value: New reference.
Return the ith element of o, or NULL on failure. This is the equivalent of the Python expression o[i].
您从 PySequence
获得新引用,因为这是 PySequence
协议定义的内容。
然而,有充分的理由以这种方式定义协议:并非所有序列都由内存支持(如 list
、tuple
),因为某些项目是动态创建的(像 range
, unicode
).
对于 list
和 tuple
所有项目都归 list
/tuple
所有(它们不是临时对象)所以我们可以借用它们(借用是一个小优化)- list
/tuple
最终会释放内存。
range
是序列的另一个例子。它实现了 PySequence-protocol):
static PySequenceMethods range_as_sequence = {
(lenfunc)range_length, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
(ssizeargfunc)range_item, /* sq_item */
0, /* sq_slice */
0, /* sq_ass_item */
0, /* sq_ass_slice */
(objobjproc)range_contains, /* sq_contains */
};
然而,PySequence_GetItem
返回的对象是临时对象(即没有人在函数外拥有对它的引用),我们可以在range_item
的源代码中验证这一点:
static PyObject *
range_item(rangeobject *r, Py_ssize_t i)
{
PyObject *res, *arg = PyLong_FromSsize_t(i);
if (!arg) {
return NULL;
}
res = compute_range_item(r, arg);
Py_DECREF(arg);
return res;
}
其中 compute_range_item
归结为 compute_item
:
static PyObject *
compute_item(rangeobject *r, PyObject *i)
{
PyObject *incr, *result;
/* PyLong equivalent to:
* return r->start + (i * r->step)
*/
incr = PyNumber_Multiply(i, r->step);
if (!incr)
return NULL;
result = PyNumber_Add(r->start, incr);
Py_DECREF(incr);
return result;
}
没有人拥有返回的对象 result
,因此接收方必须注意减少引用计数。
也许还有其他可能的解决方案(某种已创建项目的缓存),但返回新引用是处理即时创建项目问题的最simple/transparent方法。