首页 > 解决方案 > Python CApi 引用计数详细信息

问题描述

我在这里查看一些示例代码(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"));

作者解释说 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;

我的问题是,如果第二个示例类似于第一个传递 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"));

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

标签: pythonpython-c-apireference-counting

解决方案


它会导致内存泄漏。

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

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

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

第二个示例:当您将新对象传递给不窃取引用(创建新引用)的函数时,例如PySequence_SetItem,然后调用Py_DECREF它,引用计数递增到 2 并递减回 1。所以,当元组最终被销毁并decrefs其所有元素,计数将降至0。一切都好起来了。


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

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


推荐阅读