在给 Python 开发一个 C 扩展模块时,引用计数全靠手动管理吗?

Python 新手,看了一下 API,对象转字符串需要分两步走,第一步 PyObject_Str 弄出来一个 PyObject,第二步才能从这个临时的 PyObject 拿到 char *的 C-string.

但这个中间的对象我又不敢递减计数,还得等这个 C-string 用完了。。

有点儿麻烦啊,大家平时都这么凭肉眼计数吗。


在给 Python 开发一个 C 扩展模块时,引用计数全靠手动管理吗?
6 回复

不要自己直接用 Python 接口写,可以用类似 cython 的东西。


在给Python开发C扩展模块时,引用计数确实需要手动管理,但Py_INCREF和Py_DECREF这两个宏是你的主要工具。Python的C API不提供自动的垃圾回收,所以你得自己跟踪每个PyObject的引用。

简单说,当你创建一个新引用或者存储一个对象时,要调用Py_INCREF增加引用计数。当你不再需要这个引用时,必须调用Py_DECREF来减少计数。如果计数降到零,解释器会立刻释放对象内存。忘了DECREF会导致内存泄漏,而提前DECREF则可能引发段错误。

这里有个典型例子,写一个返回新列表的扩展函数:

#include <Python.h>

static PyObject* myfunc_newlist(PyObject* self, PyObject* args) {
    PyObject* new_list = PyList_New(0); // 新建列表,初始引用计数为1
    if (!new_list) return NULL; // 检查内存分配失败

    PyObject* item = PyLong_FromLong(42);
    if (!item) {
        Py_DECREF(new_list); // 创建item失败,需要释放已分配的列表
        return NULL;
    }

    if (PyList_Append(new_list, item) < 0) {
        Py_DECREF(item);
        Py_DECREF(new_list);
        return NULL;
    }
    Py_DECREF(item); // PyList_Append增加了item的引用,所以这里要减掉我们的引用

    // 函数返回new_list,调用者将获得一个引用,所以我们不需要额外INCREF
    return new_list;
}

关键规则:函数返回一个PyObject时,通常应该返回一个“新引用”(引用计数已包含这次返回)。像PyList_New这样的构造函数返回的就是新引用。而像PyList_GetItem这样的函数返回的是“借用引用”,你不能对它调用DECREF。

对于借用引用,如果你需要长期保存,必须调用Py_INCREF把它变成新引用,并在最后DECREF。管理好这些引用是C扩展开发的核心。

总结:手动管理是必须的,但遵循Python C API的引用所有权约定能让事情更清晰。

你的 C 模块要接受 Python 什么样的数据结构? list,dict 这种高级数据结构吗,还是说只是字符串这种简单的。

c 代码编译成动态库,然后让 python 调用? 或者把核心组件用 python 写,用 cython 编译成 pyd(win 系统)或.so(linux),给其它 python 程序使用。

多看看官方文档,要区分 borrowed reference 和 new reference 的区别,按照 cpython 的源码规范来,引用计数管理比较简单的

如果可以使用 C++ 的话试试 pybind11 ?

也需要跟 list,dict 打交道。如果只跟 str 打交道的话,那直接一步 PyUnicode_UnicodeAsXX 就行了,对吧? 至于 C 和 python 谁调用谁,还得听上面的,我是个小兵。

回到顶部