为什么 PySequence_GetItem return 是一个新的引用?

Why does PySequence_GetItem return a new reference?

我被 C/C++ python 扩展中的内存泄漏所困扰,因为我假设 PySequence_GetItem 会 return 一个借用的引用,在与 PyList_GetItemPyTuple_GetItem 相同。我的问题是:为什么 PySequence_GetItem return 是新引用而 PyList_GetItemPyTuple_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 协议定义的内容。

然而,有充分的理由以这种方式定义协议:并非所有序列都由内存支持(如 listtuple),因为某些项目是动态创建的(像 range, unicode).

对于 listtuple 所有项目都归 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方法。