Python引用计数与ctypes

5 投票
2 回答
2519 浏览
提问于 2025-04-16 05:28

你好,

我在理解Python的引用计数时遇到了一些问题。我想通过ctypes模块从C++返回一个元组到Python。

C++代码:

PyObject* foo(...)
{

  ...
  return Py_BuildValue("(s, s)", value1, value2);
}

Python代码:

pointer = c_foo(...) # c_foo loaded with ctypes
obj = cast(pointer, py_object).value

我不太确定对象的引用计数,所以我尝试使用sys.getrefcount(),结果得到了3。我觉得应该是2(因为getrefcount函数自己会增加一个引用计数)。

现在我不能在C++中返回之前调用Py_DECREF(),因为对象会被删除。我可以在Python中减少引用计数吗?

编辑

当调用转换函数时,引用计数会发生什么变化?我从下面的文档中不太确定。 http://docs.python.org/library/ctypes.html#ctypes.cast

ctypes.cast(obj, type) 这个函数类似于C语言中的转换操作符。它返回一个新的类型实例,这个实例指向与obj相同的内存块。type必须是一个指针类型,而obj必须是可以被解释为指针的对象。

2 个回答

4

你的C++代码看起来像是一个经典的包装器,使用了官方的C-API,这有点奇怪,因为通常在Python中使用ctypes是为了处理经典的C类型(比如int、float等)。

我个人是单独使用C-API(不使用ctypes),根据我的经验,在这种情况下你不需要担心引用计数,因为你是通过Py_BuildValue返回一个原生的Python类型。当一个函数返回一个对象时,返回对象的所有权会转移给调用这个函数的地方。

你只需要在想要改变对象的所有权时,关注Py_XINCREF/Py_XDECREF(比Py_INCREF/Py_DECREF更好,因为它可以处理NULL指针):

举个例子,你在Python中创建了一个地图的包装器(我们称这个类型的对象为py_map)。地图中的元素是C++类Foo的对象,而你又为它们创建了另一个Python包装器(我们称之为py_Foo)。如果你创建一个包装了[]操作符的函数,你将会在Python中返回一个py_Foo对象:

F = py_Map["key"]

但是由于所有权转移给了调用函数,当你删除F时,析构函数会被调用,这样C++中的地图就会包含一个指向已释放对象的指针!

解决方案是在C++中在[]的包装器里写:

...
PyObject* result; // My py_Foo object
Py_XINCREF(result); // transfer the ownership
return result;
}

你应该了解一下Python中的借用引用和拥有引用的概念。这对于正确理解引用计数是非常重要的。

5

经过进一步的研究,我发现可以指定函数的返回类型。http://docs.python.org/library/ctypes.html#callback-functions 这样一来,就不需要进行类型转换了,引用计数的问题也不再存在。

clib = ctypes.cdll.LoadLibrary('some.so')
c_foo = clib.c_foo
c_foo.restype = ctypes.py_object

由于没有其他的答案,我就把我的新解决方案当作答案接受了。

撰写回答