我在一个C/C++ python扩展中发现了一个内存泄漏,因为我假设PySequence_GetItem会像PyList_GetItem和PyTuple_GetItem一样返回一个借用的引用。我的问题是:为什么PySequence_GetItem返回新引用,而PyList_GetItem和PyTuple_GetItem返回借用的引用?
从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].发布于 2019-09-04 23:57:31
您可以从PySequence获得新的引用,因为这是PySequence-protocol定义的。
然而,以这种方式定义协议有很好的理由:并不是所有的序列都是由内存支持的(如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中的对象,因此接收方必须注意减少引用计数。
也许还有其他可能的解决方案(创建项的某种缓存),但返回一个新的引用是处理动态创建项问题的最简单/透明的方法。
https://stackoverflow.com/questions/57783652
复制相似问题