python类的mod_PY08-06:Python的类扩展

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

C++扩展类实现

基本的头文件

#include

#include

#include

#include

iostream用户标准IO输入输出

这里只使用标准输出:std::cout

sstream是字符串处理/内存处理

字符串拷贝

Python的C API接口实现

大部分API都是这个头文件一共声明。

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 "." */

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

#include

#include

#include

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;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值