You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

能否通过Python C API构建可在Python中作为常规类使用的C++类?

如何通过Python C API用C++实现可在Python中使用的类?

当然可以!虽然Python C API里没有你提到的PyClassDef,但咱们可以用PyTypeObject这个核心结构体来实现——这可是Python内部定义所有类的底层机制,完全能把C++写的类包装成Python里的常规类来用。下面我一步步给你讲具体怎么做:

核心逻辑:用PyTypeObject描述Python类

所有Python类(包括内置的listdict)都是通过PyTypeObject结构体来定义元信息的。我们要做的就是把C++类的构造、析构、方法等逻辑,绑定到这个结构体的对应字段上,让Python能识别并调用它们。

1. 先写你的C++类,再用Python对象包装它

首先定义你的C业务类,然后创建一个Python对象结构体来持有C实例的指针——这是连接C++和Python的桥梁:

#include <Python.h>

// 你的C++底层类
class MyCppClass {
public:
    int value;
    MyCppClass(int initial_val) : value(initial_val) {}
    int add(int num) { return value + num; }
    void set_value(int new_val) { value = new_val; }
};

// 对应的Python对象结构体,必须包含PyObject_HEAD宏
typedef struct {
    PyObject_HEAD
    MyCppClass* cpp_instance; // 指向C++实例的指针
} PyMyCppClass;

2. 实现Python类的构造函数(__init__

这个函数负责解析Python传入的参数,创建C++实例,并把它关联到Python对象上:

static int PyMyCppClass_init(PyMyCppClass* self, PyObject* args, PyObject* kwds) {
    int initial_val;
    // 定义关键字参数列表
    static char* kwlist[] = {"initial_val", NULL};
    
    // 解析Python传入的参数
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &initial_val)) {
        return -1; // 解析失败会自动设置Python异常
    }
    
    // 创建C++实例
    self->cpp_instance = new MyCppClass(initial_val);
    return 0;
}

3. 实现析构函数,避免内存泄漏

一定要记得在Python对象被回收时,释放C++实例的内存:

static void PyMyCppClass_dealloc(PyMyCppClass* self) {
    // 释放C++实例
    delete self->cpp_instance;
    // 调用Python的默认内存释放逻辑
    Py_TYPE(self)->tp_free((PyObject*)self);
}

4. 把C++方法绑定成Python类方法

比如把C++的addset_value暴露给Python:

// 绑定add方法
static PyObject* PyMyCppClass_add(PyMyCppClass* self, PyObject* args) {
    int num;
    if (!PyArg_ParseTuple(args, "i", &num)) {
        return NULL;
    }
    int result = self->cpp_instance->add(num);
    return PyLong_FromLong(result);
}

// 绑定set_value方法
static PyObject* PyMyCppClass_set_value(PyMyCppClass* self, PyObject* args) {
    int new_val;
    if (!PyArg_ParseTuple(args, "i", &new_val)) {
        return NULL;
    }
    self->cpp_instance->set_value(new_val);
    Py_RETURN_NONE; // 返回Python的None
}

// 方法列表,格式和PyMethodDef一致
static PyMethodDef PyMyCppClass_methods[] = {
    {"add", (PyCFunction)PyMyCppClass_add, METH_VARARGS, "Add a number to the internal value"},
    {"set_value", (PyCFunction)PyMyCppClass_set_value, METH_VARARGS, "Set the internal value"},
    {NULL} // 列表结束标记
};

5. 定义完整的PyTypeObject结构体

把上面的所有组件整合到这个结构体里,告诉Python这是一个类:

static PyTypeObject PyMyCppClass_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "my_cpp_module.MyCppClass", // 类的全名:模块名.类名
    .tp_basicsize = sizeof(PyMyCppClass),
    .tp_itemsize = 0,
    .tp_dealloc = (destructor)PyMyCppClass_dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // 允许继承
    .tp_doc = "A C++ class wrapped for Python use", // 类的文档字符串
    .tp_methods = PyMyCppClass_methods,
    .tp_init = (initproc)PyMyCppClass_init,
    .tp_new = PyType_GenericNew, // 使用Python默认的对象创建逻辑
};

6. 注册模块,让Python能找到这个类

最后把这个类注册到Python模块中,编译成扩展模块后就能用了:

// 模块定义
static PyModuleDef my_cpp_module = {
    PyModuleDef_HEAD_INIT,
    .m_name = "my_cpp_module",
    .m_doc = "Module containing a wrapped C++ class",
    .m_size = -1, // 表示模块不支持子解释器状态
};

// 模块初始化函数,必须命名为PyInit_<模块名>
PyMODINIT_FUNC PyInit_my_cpp_module(void) {
    PyObject* module;
    
    // 初始化类型对象
    if (PyType_Ready(&PyMyCppClass_Type) < 0) {
        return NULL;
    }
    
    // 创建模块对象
    module = PyModule_Create(&my_cpp_module);
    if (!module) {
        return NULL;
    }
    
    // 将类添加到模块中
    Py_INCREF(&PyMyCppClass_Type);
    PyModule_AddObject(module, "MyCppClass", (PyObject*)&PyMyCppClass_Type);
    
    return module;
}

在Python中使用这个类

编译成扩展模块后,你就可以像用普通Python类一样使用它了:

import my_cpp_module

# 创建实例
obj = my_cpp_module.MyCppClass(10)
# 调用方法
print(obj.add(5))  # 输出15
obj.set_value(20)
print(obj.add(5))  # 输出25

一些注意事项

  • 内存安全:一定要在析构函数里释放C实例,否则会造成内存泄漏。如果C类里有复杂的资源(比如文件句柄、动态内存),也要在析构时一并清理。
  • 异常处理:如果C++代码可能抛出异常,一定要在绑定的Python函数里捕获,并转换成Python异常(比如用PyErr_SetString设置RuntimeError),否则会导致Python崩溃。
  • 继承支持:如果想让这个类能被Python子类继承,要确保tp_flags里包含Py_TPFLAGS_BASETYPE,还可以通过tp_base字段指定它的父类(比如&PyBaseObject_Type)。

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

火山引擎 最新活动