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>
-
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模拟旋转图像
- 这里实现了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)
- 执行后结果如下:
-
这么繁琐的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;
}