You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从C库向Python传递数据结构体的技术实现与内存管理问题

无需手动内存管理的Python C扩展结构体传递方案

嘿,我之前做Python C扩展的时候也遇到过一模一样的问题——不想跟手动内存分配打交道,生怕漏了free导致泄漏,或者搞错引用计数崩掉。给你几个亲测靠谱的方案,全程让Python解释器帮你管内存:

方案1:用Py_BuildValue一键转字典(最省心)

如果你的结构体字段不多,Py_BuildValue绝对是首选。它可以直接把C结构体的字段映射成Python字典,而且创建的字典对象完全由Python解释器托管,不用你手动管内存。

举个实际例子,假设你的C结构体是这样的:

typedef struct {
    const char* item_name;
    int item_id;
    float price;
} Product;

在你的扩展函数里,只需要这么写:

static PyObject* get_product(PyObject* self, PyObject* args) {
    // 假设这里已经从输入字符串解析出了Product实例product
    Product product = {"Wireless Mouse", 1001, 29.99};

    // 用Py_BuildValue构造Python字典,直接返回即可
    return Py_BuildValue("{s:s, s:i, s:f}", 
                        "item_name", product.item_name,
                        "item_id", product.item_id,
                        "price", product.price);
}

解释一下:{s:s, s:i, s:f}是格式字符串,对应“键-值”对的类型,s代表字符串,i是整数,f是浮点数。这个函数会自动创建字典对象,返回后Python的GC会负责回收内存,完全不用你操心。

方案2:手动构造字典(复杂结构体适用)

如果你的结构体有嵌套、动态长度字段,或者需要更精细的控制,可以手动用Python C API构造字典。核心还是依赖解释器管理内存,你只需要正确处理临时对象的引用计数:

static PyObject* get_product(PyObject* self, PyObject* args) {
    Product product = {"Mechanical Keyboard", 1002, 89.99};

    // 创建空字典,内存由解释器管理
    PyObject* product_dict = PyDict_New();
    if (!product_dict) return NULL; // 分配失败直接返回NULL,解释器会处理错误

    // 将C字符串转成Python字符串对象
    PyObject* name_obj = PyUnicode_FromString(product.item_name);
    if (name_obj) {
        PyDict_SetItemString(product_dict, "item_name", name_obj);
        Py_DECREF(name_obj); // 字典已经持有引用,释放我们创建的临时引用
    } else {
        Py_DECREF(product_dict); // 字符串创建失败,要先释放字典再返回
        return NULL;
    }

    // 整数转Python对象
    PyObject* id_obj = PyLong_FromLong(product.item_id);
    if (id_obj) {
        PyDict_SetItemString(product_dict, "item_id", id_obj);
        Py_DECREF(id_obj);
    } else {
        Py_DECREF(product_dict);
        return NULL;
    }

    // 浮点数转Python对象
    PyObject* price_obj = PyFloat_FromDouble(product.price);
    if (price_obj) {
        PyDict_SetItemString(product_dict, "price", price_obj);
        Py_DECREF(price_obj);
    } else {
        Py_DECREF(product_dict);
        return NULL;
    }

    return product_dict; // 返回字典,解释器接管内存
}

这里要注意:临时创建的name_obj、id_obj这些对象,在添加到字典后必须调用Py_DECREF,因为字典会自动增加它们的引用计数,我们自己创建的那个引用需要释放掉,避免内存泄漏。但字典本身不需要手动释放,返回后解释器会处理。

方案3:结构体数组转Python列表

如果你的返回数据是结构体数组,用PyList_New创建列表,再把每个结构体转成字典添加进去,同样不用管内存:

static PyObject* get_product_list(PyObject* self, PyObject* args) {
    Product products[] = {
        {"Wireless Mouse", 1001, 29.99},
        {"Mechanical Keyboard", 1002, 89.99},
        {"USB-C Cable", 1003, 9.99}
    };
    int product_count = sizeof(products)/sizeof(products[0]);

    // 创建指定长度的列表
    PyObject* product_list = PyList_New(product_count);
    if (!product_list) return NULL;

    for (int i=0; i<product_count; i++) {
        PyObject* product_dict = Py_BuildValue("{s:s, s:i, s:f}",
                                            "item_name", products[i].item_name,
                                            "item_id", products[i].item_id,
                                            "price", products[i].price);
        if (!product_dict) {
            Py_DECREF(product_list); // 有一个字典创建失败,先释放列表
            return NULL;
        }
        // PyList_SetItem会接管product_dict的引用,所以不需要Py_DECREF
        PyList_SetItem(product_list, i, product_dict);
    }

    return product_list;
}

核心原则总结

不管用哪种方案,核心都是尽量使用Python C API提供的内置对象(字典、列表、字符串、数字等),这些对象的内存完全由Python解释器的垃圾回收机制管理,你只需要遵守引用计数的基本规则(比如临时对象的Py_DECREF),完全不需要手动调用malloc/free,从根源上避免内存管理问题。

内容的提问来源于stack exchange,提问作者rutri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:18:42