Skip to content

Python 使用 C/C++ 扩展类模块和之前所讲的模块差不多,多了一些流程,比如:编写类的构造函数、析构函数、成员属性、成员方法、以及成员属性和方法绑定。

随着扩展的模块越来越复杂,不得不了解 Python 中的引用计数规则,更加规范的管理内存使用,防止内存泄露。

如下是两个流程,由简入繁的了解类模块开发的整个流程。

扩展开发流程,小试牛刀

编写 C/C++ 代码

c
// custom.cpp
#include "Python.h"

typedef struct {
    PyObject_HEAD
} CustomObject;

static PyTypeObject CustomType = {
        PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom.Custom",
        .tp_doc = "Custom objects",
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT,
        .tp_new = PyType_GenericNew,
};

static PyModuleDef custommodule = {
        PyModuleDef_HEAD_INIT,
        .m_name = "custom",
        .m_doc = "example module that creates an extension type.",
        .m_size = -1,
};

PyMODINIT_FUNC PyInit_custom(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

编写 setup.py 安装程序

编写 setup.py 之后执行python setup.py install,编译模块十分的方便,可以将我们的动态链接库快速导入到系统模块中,方便我们导入自己的模块。

python
# setup.py
from distutils.core import setup, Extension
setup(name="custom", version="1.0",
      ext_modules=[Extension("custom", ["custom.cpp"])])

执行安装测试

调用模块,以及初始化对象

python setup.py install

>>> import custom
>>> 
>>> print(custom.Custom())
<custom.Custom object at 0x7f8552874d50>
>>>

深入理解,成员属性成员方法注册

类的成员属性和方法定义及绑定流程

编写 C/C++ 代码

c
#include "Python.h"
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;         // 定义新的结构体,Python 中的类

// Custom_new 初始化函数
static PyObject* Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->number = 0;
    }
    return (PyObject *) self;
}

// Custom_dealloc 析构函数,释放对象
static void Custom_dealloc(CustomObject *self)
{
    Py_XDECREF(self->first);                    // 释放成员 first
    Py_XDECREF(self->last);                     // 释放成员 last
    Py_TYPE(self)->tp_free((PyObject *)self);   // 释放结构体 CustomObject
}

// Custom_init 对象初始化 cs = custom.Custom()
static int Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    // 定义参数
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;
    // 参数绑定
    if (!PyArg_ParseTupleAndKeywords(
            args, kwds, "|OOi", kwlist, &first, &last, &self->number))
        return -1;
    // 参数赋值绑定
    if (first) {
        tmp = self->first;
        Py_INCREF(first);       // 引用计数器累加
        self->first = first;
        Py_XDECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);        // 引用计数器累加
        self->last = last;
        Py_XDECREF(tmp);
    }
    return 0;
}

// 定义成员属性;CustomObject 的成员属性
static PyMemberDef Custom_members[] = {
        {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
                "first name"},
        {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
                "last name"},
        {"number", T_INT, offsetof(CustomObject, number), 0,
                "custom number"},
        {NULL}  /* Sentinel */
};

// Custom_name 定义方法
static PyObject *Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }
    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

// Custom_methods 定义类的方法,并绑定 Custom_name
static PyMethodDef Custom_methods[] = {
        {"name", (PyCFunction) Custom_name, METH_NOARGS,
         "Return the name, combining the first and last name"
        },
        {NULL}  /* Sentinel */
};

static PyTypeObject CustomType = {
        PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom.Custom",
        .tp_doc = "Custom objects",
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT,
        .tp_new = PyType_GenericNew,
        .tp_init = (initproc) Custom_init,              // 绑定初始化函数,构造函数
        .tp_dealloc = (destructor) Custom_dealloc,      // 绑定析构函数
        .tp_members = Custom_members,                   // 绑定成员属性
        .tp_methods = Custom_methods,                   // 绑定成员方法
};

static PyModuleDef custommodule = {
        PyModuleDef_HEAD_INIT,
        .m_name = "custom",
        .m_doc = "example module that creates an extension type.",
        .m_size = -1,
};

// PyInit_custom 初始化模块
PyMODINIT_FUNC PyInit_custom(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

编写测试代码

pprint 模块可以美化我们的输出,dir 可以打印模块中的成员属性和成员方法,更加直观的展示输出。['first', 'last', 'name', 'number'] 正是我们之前在 C/C++ 中定义的类属性、方法。

python
import custom
from pprint import pprint

cs = custom.Custom(first="li", last="si", number=3)
pprint(dir(cs))

print(cs.name())
print(cs.first)
print(cs.number)

结果输出

text
['__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'first',
 'last',
 'name',
 'number']
li si
li
3

总结

文章中的示例来自官方文档,官方文档比较消息的描述了开发过程,该篇增加了流程图,已经详细的注释,可以让读者更加直观的了解整个流程。

如果觉得该篇对您有所受益,还希望点赞、关注、收藏三连击,这也是我持续输出,不断提升的动力,致谢。