美文网首页
PY08-06:Python的类扩展

PY08-06:Python的类扩展

作者: 杨强AT南京 | 来源:发表于2020-05-18 08:10 被阅读0次

      实际C++的类是很难直接导出为Python的类,这缘于C++在编译的时候有一个过程就是修饰命名。但是在C中导出Python还是可以的,这个就是Python提供的C接口来实现(CPython)。这个主题就是实现一个C扩展模块提供一个Python类扩展。


    C++扩展类实现

    基本的头文件

    #include <iostream>
    #include <sstream>
    #include <Python.h>
    #include <structmember.h>
    
    1. iostream用户标准IO输入输出

      • 这里只使用标准输出:std::cout
    2. sstream是字符串处理/内存处理

      • 字符串拷贝
    3. Python的C API接口实现

      • 大部分API都是这个头文件一共声明。
    4. structmember定义了结构体的类型定义

      • T_STRING/T_INT等
      • PyMemberDef数据成员的定义

    数据成员

    数据成员定义

    • 使用结构体定义数据,这一点不奇怪。实施Python的函数,类都是用C扩展实现。实际上没有C++什么事儿,就算我们人为使用了部分C++语法,但是从底层本质来说还是是C的实现最方便。
    typedef struct _Sobel{
        PyObject_HEAD 
        char *m_filename;
        int   m_fd;
    } Sobel;
    
    

    数据成员描述

    • 数据成员描述,使用的是PyMemberDef类型数组,PyMemberDef这是一个结构体,其成员是数据成员需要描述的5个信息。
    typedef struct PyMemberDef {
        char *name;           // 成员名
        int type;             // 类型
        Py_ssize_t offset;    // 偏离位置
        int flags;            // 属性修饰标识READONLY , READ_RESTRICTED, PY_WRITE_RESTRICTED,RESTRICTED
        char *doc;           // 成员的doc文档描述
    } PyMemberDef;
    
    • 描述上面定义的数据如下
      • 最后空的一个记录,表示结束。
    
    static PyMemberDef  m_data[] = {
        {"m_filename", T_STRING, offsetof(Sobel, m_filename), 0, "bmp file name"},
        {"m_fd",       T_INT,    offsetof(Sobel, m_fd),       0, "file descriptor"},
        {NULL,         NULL,     NULL,                        0, NULL}
    };
    
    

    数据成员初始化

    • 核心还是参数分析,与返回值处理,但是初始化没有返回值。
      • 下面的逻辑是通过命名参数filename找到参数名,并初始化成员。
    static void Sobel_init(Sobel *self, PyObject*args, PyObject*kwargs){
        const char* filename;
        static char *argsname[] = {"filename", NULL};
        
        if(!PyArg_ParseTupleAndKeywords(args, kwargs, "s", argsname, &filename)){
            std::cout << "parse init parameter error!" << std::endl;
            return;
        }
        self->m_filename = new char[strlen(filename) + 1];
        errno_t er = strcpy_s(self->m_filename, strlen(filename) + 1, filename);
        std::cout << "init ok:" << self->m_filename << std::endl;
    }
    

    数据成员的释放

    • 释放是主要是释放成员的内存空间
    static void Sobel_destruct(Sobel *self){
        if(self->m_filename){
            delete[] self->m_filename;
        }
    }
    

    函数成员

    函数成员定义

    • 函数与构造器与虚构器的差别是不管有返回值没有,都有PyObject指针返回。
      • 这里实现了open与rotate(简单实现)
        • open模拟打开文件
        • rotate模拟旋转图像
    static PyObject* Sobel_open(Sobel* self){
        std::cout << "Open file ok" << std::endl;
        return Py_BuildValue("s", self->m_filename);;
    }
    
    
    static PyObject *Sobel_rotate(Sobel *self, PyObject *args){
        float  angle;
        if(!PyArg_ParseTuple(args, "f", &angle)){
            return Py_None;
        }
        std::cout << "rotate OK:" << angle << std::endl;
        return Py_None; 
    }
    

    函数成员描述

    • 描述函数使用PyMethodDef结构体,多个函数使用PyMethodDef数组来描述。
    struct PyMethodDef {
        const char  *ml_name;   /* Python中调用的函数名*/
        PyCFunction ml_meth;    /* C++实现的函数指针,只要类型转换,三种类型的函数(keywords参数函数,元组参数的函数,没有参数的函数) */
        int         ml_flags;   /* 标记用来指定函数的参数的三种方式:METH_VARARGS, METH_KEYWORDS, METH_NOARGS */
        const char  *ml_doc;    /* 函数的doc文档 */
    };
    typedef struct PyMethodDef PyMethodDef;
    
    • 上面open与rotate函数的描述
    static PyMethodDef  m_functions[] = {
        {"open",   (PyCFunction)Sobel_open,   METH_NOARGS,  "open image file of bmp format"},
        {"rotate", (PyCFunction)Sobel_rotate, METH_VARARGS, "rotate image"},
        {NULL, NULL, NULL, NULL}
    };
    

    模块初始化的工作

    • 模块的初始化只要是创建模块,并绑定一个Python的类型(元类对象:元类就是类型的类型,实例化后就是我们一般意义上的类)

    模块描述

    • 注意:这类的函数不能在模块中描述,在模块中描述的函数与数据,导出为Python的全局函数与数据。

    • 模块描述采用PyModuleDef结构体,PyModuleDef结构体定义如下:

    typedef struct PyModuleDef{
      PyModuleDef_Base m_base;    // 模块的开始地址。一般使用固定的宏PyModuleDef_HEAD_INIT
      const char* m_name;         // 模块名(安装的时候需要使用的名字)
      const char* m_doc;          // 模块的文档
      Py_ssize_t m_size;          // 模块的大小,一般使用-1表示自动确定
      PyMethodDef *m_methods;     // 全局函数
      struct PyModuleDef_Slot* m_slots;     
      traverseproc m_traverse;
      inquiry m_clear;
      freefunc m_free;
    } PyModuleDef;
    
    #ifdef __cplusplus
    }
    
    • 我们创建的模块描述如下:
    static PyModuleDef  def_module = {
        PyModuleDef_HEAD_INIT,
        "sobel",
        "This is doc for Class Sobel!",
        -1,
        NULL,    // 我们的函数没有在这儿绑定
        NULL,
        NULL,
        NULL,
        NULL
    };
    

    类型对象描述(Python类)

    • Python的类是元类的对象,所以这儿描述的是元类对象。元类对象的描述也是使用C结构体, 这个结构体太彪悍,彪悍的人生不要解释,但是可以通过可阅读的成员名,可以知道其表示的含义。
    typedef struct _typeobject {
        PyObject_VAR_HEAD
        const char *tp_name; /* For printing, in format "<module>.<name>" */
        Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
    
        /* Methods to implement standard operations */
    
        destructor tp_dealloc;
        printfunc tp_print;
        getattrfunc tp_getattr;
        setattrfunc tp_setattr;
        PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                        or tp_reserved (Python 3) */
        reprfunc tp_repr;
    
        /* Method suites for standard classes */
    
        PyNumberMethods *tp_as_number;
        PySequenceMethods *tp_as_sequence;
        PyMappingMethods *tp_as_mapping;
    
        /* More standard operations (here for binary compatibility) */
    
        hashfunc tp_hash;
        ternaryfunc tp_call;
        reprfunc tp_str;
        getattrofunc tp_getattro;
        setattrofunc tp_setattro;
    
        /* Functions to access object as input/output buffer */
        PyBufferProcs *tp_as_buffer;
    
        /* Flags to define presence of optional/expanded features */
        unsigned long tp_flags;
    
        const char *tp_doc; /* Documentation string */
    
        /* Assigned meaning in release 2.0 */
        /* call function for all accessible objects */
        traverseproc tp_traverse;
    
        /* delete references to contained objects */
        inquiry tp_clear;
    
        /* Assigned meaning in release 2.1 */
        /* rich comparisons */
        richcmpfunc tp_richcompare;
    
        /* weak reference enabler */
        Py_ssize_t tp_weaklistoffset;
    
        /* Iterators */
        getiterfunc tp_iter;
        iternextfunc tp_iternext;
    
        /* Attribute descriptor and subclassing stuff */
        struct PyMethodDef *tp_methods;
        struct PyMemberDef *tp_members;
        struct PyGetSetDef *tp_getset;
        struct _typeobject *tp_base;
        PyObject *tp_dict;
        descrgetfunc tp_descr_get;
        descrsetfunc tp_descr_set;
        Py_ssize_t tp_dictoffset;
        initproc tp_init;
        allocfunc tp_alloc;
        newfunc tp_new;
        freefunc tp_free; /* Low-level free-memory routine */
        inquiry tp_is_gc; /* For PyObject_IS_GC */
        PyObject *tp_bases;
        PyObject *tp_mro; /* method resolution order */
        PyObject *tp_cache;
        PyObject *tp_subclasses;
        PyObject *tp_weaklist;
        destructor tp_del;
    
        /* Type attribute cache version tag. Added in version 2.6 */
        unsigned int tp_version_tag;
    
        destructor tp_finalize;
    
    #ifdef COUNT_ALLOCS
        /* these must be last and never explicitly initialized */
        Py_ssize_t tp_allocs;
        Py_ssize_t tp_frees;
        Py_ssize_t tp_maxalloc;
        struct _typeobject *tp_prev;
        struct _typeobject *tp_next;
    #endif
    } PyTypeObject;
    
    • 我们实现的Python类描述如下:
    static PyTypeObject classinfo = {
        PyVarObject_HEAD_INIT(NULL, 0)
        "This is doc for Module", 
        sizeof(Sobel),
        0,
        (destructor)Sobel_destruct,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        "This is class for Sobel!",
        0,
        0,
        0,
        0,
        0,
        0,
        m_functions, 
        m_data,
        0,
        0,
        0,
        0,
        0,
        0,
        (initproc)Sobel_init, 
        0,
        PyType_GenericNew,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
    };
    
    • 注意:其中PyVarObject_HEAD_INIT(NULL, 0)的含义也是确定头地址的。
    #define PyObject_HEAD                   PyObject ob_base;
    
    #define PyObject_HEAD_INIT(type)        \
        { _PyObject_EXTRA_INIT              \
        1, type },
    
    #define PyVarObject_HEAD_INIT(type, size)       \
        { PyObject_HEAD_INIT(type) size },
    

    模块初始化函数定义

    • 这个函数按照命名预定的:PyInit_模块名
    PyMODINIT_FUNC PyInit_sobel(void){
        PyObject* mod;
        // 1. 创建模块
        // 2. 添加类型到导出的模块
        return mod;
    }
    
    

    创建模块

    • 创建模块使用PyModule_Create函数,函数原型如下
    PyAPI_FUNC(PyObject *) PyModule_Create2(struct PyModuleDef*, int apiver);
    #define PyModule_Create(module)  PyModule_Create2(module, PYTHON_API_VERSION)
    #define PYTHON_API_VERSION 1013
    
        mod = PyModule_Create(&def_module);
        if(mod == 0){
            return Py_None;
        }
        Py_INCREF(&def_module);
    
    • 创建成功,增加一次引用计数。

    添加类型对象到模块(Python类)

    • 添加模块使用函数PyModule_AddObject实现,函数定义如下:
    PyAPI_FUNC(int) PyModule_AddObject(PyObject *, const char *, PyObject *);
    
    • 在添加Python类之前,建议检测下是否描述完备,这个函数是PyType_Ready
    PyAPI_FUNC(int) PyType_Ready(PyTypeObject *);
    
    • 我们把我们描述的类直接加入即可:
        if(PyType_Ready(&classinfo) < 0){
            return Py_None;
        }
        PyModule_AddObject(mod, "Sobel", (PyObject*)&classinfo);
    

    完整的模块导出的初始化工作实现

    PyMODINIT_FUNC PyInit_sobel(void){
        PyObject* mod;
        if(PyType_Ready(&classinfo) < 0){
            return Py_None;
        }
        mod = PyModule_Create(&def_module);
        if(mod == 0){
            return Py_None;
        }
        Py_INCREF(&def_module);
        PyModule_AddObject(mod, "Sobel", (PyObject*)&classinfo);
        return mod;
    }
    

    编译与测试

    编译脚本setup.py

    from distutils.core import setup, Extension
    setup(
        name="=sobel",
        version="1.0",
        ext_modules=[
            Extension("sobel", sources=["sobel.cpp"], language="C++"), 
        ]
    )
    
    

    编译命令

    • 命令:

      • python setup.py build_ext --inplace
    • 编译过程截图

    编译过程
    • 编译后生成动态库文件:
      • sobel.cp36-win_amd64.pyd

    测试Python程序test.py

    from sobel import *
    
    # help(Sobel)
    s = Sobel("gpu.bmp")
    print("调用返回:",s.open())
    s.rotate(45)
    
    
    • 执行后结果如下:
    在Python中执行的结果
    • 这么繁琐的C扩展,我想很多人内心是顽皮马儿跑过,所以还有一个东西可以考虑学学Cython。实际Cython的性能是否真比C高,这个估计需要评估下,使用C是王道啊。

    • 导出的文档帮助如下

    from sobel import *
    
    help(Sobel)
    
    Help on class This is doc for Module in module builtins:
    
    class This is doc for Module(object)
     |  This is class for Sobel!
     |  
     |  Methods defined here:
     |  
     |  __init__(self, /, *args, **kwargs)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |  
     |  open(...)
     |      open image file of bmp format
     |  
     |  rotate(...)
     |      rotate image
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  m_fd
     |      file descriptor
     |  
     |  m_filename
     |      bmp file name
    

    附录:

    说明

    • 本主题的内容还是没有实现C++的类被导出到Python,仅仅是利用C实现了一个Python类而已。

      • 要导出C++类还需要二次调用。
    • 官方的文档地址如下:

      • https://docs.python.org/dev/c-api/index.html

    完整的代码:sobel.cpp

    • 这个代码使用C也可以一样实现,使用C++是我们估计的,这个里面的额核心语法都是C。
    #include <iostream>
    #include <sstream>
    #include <Python.h>
    #include <structmember.h>
    
    typedef struct _Sobel{
        PyObject_HEAD 
        char *m_filename;
        int   m_fd;
    } Sobel;
    
    
    static PyMemberDef  m_data[] = {
        {"m_filename", T_STRING, offsetof(Sobel, m_filename), 0, "bmp file name"},
        {"m_fd",       T_INT,    offsetof(Sobel, m_fd),       0, "file descriptor"},
        {NULL,         NULL,     NULL,                        0, NULL}
    };
    
    static void Sobel_init(Sobel *self, PyObject*args, PyObject*kwargs){
        const char* filename;
        static char *argsname[] = {"filename", NULL};
        
        if(!PyArg_ParseTupleAndKeywords(args, kwargs, "s", argsname, &filename)){
            std::cout << "parse init parameter error!" << std::endl;
            return;
        }
        self->m_filename = new char[strlen(filename) + 1];
        errno_t er = strcpy_s(self->m_filename, strlen(filename) + 1, filename);
        std::cout << "init ok:" << self->m_filename << std::endl;
    }
    
    static void Sobel_destruct(Sobel *self){
        if(self->m_filename){
            delete[] self->m_filename;
        }
    }
    
    static PyObject* Sobel_open(Sobel* self){
        std::cout << "Open file ok" << std::endl;
        return Py_BuildValue("s", self->m_filename);;
    }
    
    static PyObject *Sobel_rotate(Sobel *self, PyObject *args){
        float  angle;
        if(!PyArg_ParseTuple(args, "f", &angle)){
            return Py_None;
        }
        std::cout << "rotate OK:" << angle << std::endl;
        return Py_None; 
    }
    
    static PyMethodDef  m_functions[] = {
        {"open",   (PyCFunction)Sobel_open,   METH_NOARGS,  "open image file of bmp format"},
        {"rotate", (PyCFunction)Sobel_rotate, METH_VARARGS, "rotate image"},
        {NULL, NULL, NULL, NULL}
    };
    
    static PyTypeObject classinfo = {
        PyVarObject_HEAD_INIT(NULL, 0)
        "This is doc for Module", 
        sizeof(Sobel),
        0,
        (destructor)Sobel_destruct,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        "This is class for Sobel!",
        0,
        0,
        0,
        0,
        0,
        0,
        m_functions, 
        m_data,
        0,
        0,
        0,
        0,
        0,
        0,
        (initproc)Sobel_init, 
        0,
        PyType_GenericNew,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
    };
    
    static PyModuleDef  def_module = {
        PyModuleDef_HEAD_INIT,
        "sobel",
        "This is doc for Class Sobel!",
        -1,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL
    };
    
    PyMODINIT_FUNC PyInit_sobel(void){
        PyObject* mod;
        if(PyType_Ready(&classinfo) < 0){
            return Py_None;
        }
        mod = PyModule_Create(&def_module);
        if(mod == 0){
            return Py_None;
        }
        Py_INCREF(&def_module);
        PyModule_AddObject(mod, "Sobel", (PyObject*)&classinfo);
        return mod;
    }
    
    

    相关文章

      网友评论

          本文标题:PY08-06:Python的类扩展

          本文链接:https://www.haomeiwen.com/subject/qmuyohtx.html