Python CApi 引用计数详细信息

Dan*_*arb 4 python reference-counting python-c-api

我在这里查看一些示例代码(https://docs.python.org/2.0/api/refcountDetails.html)并试图更好地理解两个示例之间的差异:第一个示例是:

PyObject *t;

t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyInt_FromLong(1L));
PyTuple_SetItem(t, 1, PyInt_FromLong(2L));
PyTuple_SetItem(t, 2, PyString_FromString("three"));
Run Code Online (Sandbox Code Playgroud)

作者解释说 PyTuple_SetItem()窃取了引用(因此无需 DECREF)。 好吧,我明白了。然后作者使用 PySequence_SetItem() 提供了类似的代码,它不会窃取引用,因此调用者必须 DECREF,示例代码如下所示:

PyObject *l, *x;

l = PyList_New(3);
x = PyInt_FromLong(1L);
PySequence_SetItem(l, 0, x); Py_DECREF(x);
x = PyInt_FromLong(2L);
PySequence_SetItem(l, 1, x); Py_DECREF(x);
x = PyString_FromString("three");
PySequence_SetItem(l, 2, x); Py_DECREF(x);
PyObject *l, *x;
Run Code Online (Sandbox Code Playgroud)

我的问题是,如果第二个示例与第一个示例类似,传递 PyTYPE_FromSOMETYPE 如下,会发生什么?

PyObject *l;

l = PyList_New(3);
PySequence_SetItem(l, 0, PyInt_FromLong(1L));
PySequence_SetItem(l, 1, PyInt_FromLong(2L));
PySequence_SetItem(l, 2, PyString_FromString("three"));
Run Code Online (Sandbox Code Playgroud)

最后一种情况是良性的,还是会导致内存泄漏(因为 PySequence_SetItem 不会取得 PyInt_FromLong 和 PyString_FromString 创建的引用的所有权,调用者也不会 DECREF 它)?

aba*_*ert 5

它会导致内存泄漏。

当您创建一个对象时,它以 1 的引用计数开始。只有当引用计数变为 0 时,该对象才会被删除。

第一个例子:当你将新对象传递给一个窃取引用(获取所有权)的函数时,例如PyTuple_SetItem,引用计数不会增加,因此它仍然是 1。当元组最终被销毁并减少其所有元素的引用时,计数将下降到0,所以它会被摧毁。一切都很好。

第三个示例:当您将新对象传递给不窃取引用(创建新引用)的函数(例如 )时,PySequence_SetItem引用计数递增,因此为 2。当元组最终被销毁并减少其所有元素的引用时, count 将降至 1,因此不会被销毁。而且,由于没有其他人再引用它(除非您将它存储在某个地方),所以任何人都无法删除它。所以就泄露了

第二个示例:当您将新对象传递给不窃取引用(创建新引用)的函数(例如 )PySequence_SetItem,但随后调用Py_DECREF它时,引用计数将递增到 2 并递减回 1。因此,当元组最终被破坏并减少其所有元素,计数将降至 0。一切又恢复正常。


如果你想知道为什么 Python 会同时使用任何非窃取函数,你只需要考虑一个不那么简单的情况。

如果您想将项目放入两个元组而不是一个元组中该怎么办?或者,如果您想将其放入元组中,但也将其存储在 C 静态指针中,或某些模块的全局变量中,或其他地方?如果您想将引用计数存储在两个位置,则希望引用计数增加 2,而当局部变量消失时,它们会减少 1。对于非常简单的情况,即您只是创建一些内容并立即将其移交,引用窃取函数可以让您避免“一递增一递减”,并且对于单行来说也很方便。但对于更复杂的事情来说,这是没有意义的。

  • @DanielGoldfarb 对。这就是为什么只要我们 decref 前一个指针,就可以继续为下一个指针重用相同的变量“x”。 (2认同)