PY08-06:Python的类扩展

2020-05-18  本文已影响0人  杨强AT南京

  实际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数据成员的定义

数据成员

数据成员定义

typedef struct _Sobel{
    PyObject_HEAD 
    char *m_filename;
    int   m_fd;
} Sobel;

数据成员描述

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}
};

数据成员初始化

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; 
}

函数成员描述

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;
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}
};

模块初始化的工作

模块描述

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类)

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;
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
};
#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 },

模块初始化函数定义

PyMODINIT_FUNC PyInit_sobel(void){
    PyObject* mod;
    // 1. 创建模块
    // 2. 添加类型到导出的模块
    return mod;
}

创建模块

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类)

PyAPI_FUNC(int) PyModule_AddObject(PyObject *, const char *, PyObject *);
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程序test.py

from sobel import *

# help(Sobel)
s = Sobel("gpu.bmp")
print("调用返回:",s.open())
s.rotate(45)

在Python中执行的结果
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

附录:

说明

完整的代码:sobel.cpp

#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;
}


上一篇下一篇

猜你喜欢

热点阅读