C++导出类到Python

时间:2022-04-05 20:16:42

C++导出类到Python

                                                                                           点击下载源码  

上次稍微谈了下如何用C++写的函数来扩展Python,使Python很方便的调用用C++写的高效的、快速的函数。我们知道——Python是面向对象的,那么我们是否可以用C++写的类来扩展Python程序呢?答案是可以的!首先说明:我的Python3.1的,与2.x在一些Python/C API有区别。

 

 

       一、简介  

比如我们有一个C++类——用于记录一个学生的信息:姓名、数学成绩、英语成绩,呃,就这些吧!也许你会说这不是没事找事么?Python也可以很简单、很快的实现这个类。但我要说的是——我只是想用这个小例子来阐述C++类扩展Python”的主要思想,达到举一反三的效果。只要学会了这个例子,就可以将任意复杂的类导出到Python环境供Python调用。

 

       这个类的大致雏形如下,用C++

      

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. class CScore  
  2. {  
  3. public:  
  4.      CScore();  
  5.      ~CScore();  
  6.   
  7.      //一些成员函数的定义处...  
  8.   
  9. private:  
  10.      char* m_szName;  
  11.      float m_fMath;  
  12.      float m_fEnglish;  
  13. };  

 

 

Python类写这个类:

 

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. class CScore:  
  2.        def __init__(Self):  
  3.               m_szName = "";  
  4.               m_fMath  = 0.0;  
  5.               m_English  = 0.0;  
  6.   
  7.        #一些成员函数的定义处…  
  8.          
  9.         def __del__(Self):  
  10.               pass  

 

也很简单,就是这些成员函数的定义也大致相同。如果你能看懂上面两个不同语言定义的类,那么请继续!不然可能有些吃力。下面我们来实现这样一个操作:“用C++语言写这样一个CScore类,可以在Python Intepreter中方便的调用”。 OKLet’s Go

 

二、具体操作:

1> 依然新建一个Win32 DLL Project,添加一个Cpp源文件——比如是Extension_Class.cpp,保证你的Python.../Include ../Libs路径是添加到VC系统路径中的。然后逐一添加以下代码:

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <iostream>  
  2. #include <sstream>  
  3. #include <Python.h>  
  4. #include <structmember.h>  
  5. using namespace std;  

 

Python.h很熟悉,是不?structmember.h又是一个新的头文件,需要用到里面的数据结构。其余不解释!

 

 

2> 也许你会很自然的直接写一个C++类,但我们会发现,事实并不是这样。接着来:

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. ////////////////////////////////////////////////////////////  
  2. // 类/结构的定义.  
  3. typedef struct _CScore  
  4. {  
  5.        PyObject_HEAD      // == PyObject ob_base;  定义一个PyObject对象.  
  6.        char*  m_szName;  
  7.        float  m_dMath;  
  8.        float  m_dEnglish;  
  9. }CScore;  

 

可以看到我们定义了一个结构而并不是一个类。第一个PyObject_HEAD是一个宏,我们可以在PythonIncludeobject.h头文件找到它的定义:

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /* PyObject_HEAD defines the initial segment of every PyObject. */  
  2. #define PyObject_HEAD                  PyObject ob_base;  

 

大意是PyObject_HEAD是每一个PyObject的初始段。其余的成员依次是学生的名字(m_szName)、数学成绩(m_fMath)、英语成绩(m_fEnglish)

 

3> 有了类的定义还不够,还得对它的数据成员进行说明:

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. static PyMemberDef CScore_DataMembers[] =         //类/结构的数据成员的说明.  
  2.   
  3.       {"m_szName",   T_STRING, offsetof(CScore, m_szName),   0, "The Name of instance"},  
  4.       {"m_dMath",    T_FLOAT,  offsetof(CScore, m_dMath),    0, "The Math score of instance."},  
  5.       {"m_dEnglish", T_FLOAT,  offsetof(CScore, m_dEnglish), 0, "The English score of instance."},  
  6.       {"m_dTotal",   T_FLOAT,  offsetof(CScore, m_dTotal),   0, "The Total score of instance.align"},  
  7.   
  8.       {NULL, NULL, NULL, 0, NULL}  
  9. ;  

 

PyMemberDef是类成员的说明结构。我只解释其中一个成员——m_szName,其余的一样。先来这个字符串”m_szName”就是导出到Python中的类成员,这个名字不一定非得和C++中相应类成员的名字相同。

T_STRINGT_FLOAT是这个成员的数据类型的说明。不解释!

offsetof亦是一个宏,在STDDEF.h中:

       #define offsetof(s,m)   (size_t)&(((s *)0)->m)  那么offsetof(CScore, m_szName)就是: (size_t)&(((CScore*)0)->m_szName)

下来是一个字符串”The …”——这是成员的DocString其余成员依次类推。完了!

 

4> 我们知道,作为一个类,光有数据时不行的,还得有方法可供调用。下面来给这个类添加方法:

先看类的构造、析构等内置方法:

 

 

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. //////////////////////////////////////////////////////////////  
  2. // CScore类的所有内置、构造方法.  
  3. //  
  4. static void CScore_init(CScore* Self, PyObject* pArgs)    //构造方法.  
  5. {  
  6.        const char* Name = 0;  
  7.        if(!PyArg_ParseTuple(pArgs, "sff", &Name, &Self->m_dMath, &Self->m_dEnglish))  
  8.        {  
  9.               cout<<"Parse the argument FAILED! You should pass correct values!"<<endl;  
  10.               return ;  
  11.        }  
  12.   
  13.        Self->m_szName = new char[strlen(Name) + 1];  
  14.        strcpy(Self->m_szName, Name);  
  15. }  
  16.   
  17. static void CScore_Destruct(CScore* Self)                   //析构方法.  
  18. {  
  19.        if(Self->m_szName)  
  20.               delete [] Self->m_szName;              //先释放其字符指针对象.  
  21.   
  22.        //如果还有PyObject*成员的话,要一并释放之.  
  23.        //如:Py_XDECREF(Self->Member);  
  24.        Py_TYPE(Self)->tp_free((PyObject*)Self);      //释放对象/实例.  
  25. }  
  26.   
  27. static PyObject* CScore_Str(CScore* Self)             //调用str/print时自动调用此函数.  
  28. {  
  29.        ostringstream OStr;  
  30.        OStr<<"Name    : "<<Self->m_szName<<endl  
  31.            <<"Math    : "<<Self->m_dMath<<endl  
  32.            <<"English : "<<Self->m_dEnglish<<endl  
  33.        string Str = OStr.str();  
  34.        return Py_BuildValue("s", Str.c_str());  
  35. }  
  36.   
  37. static PyObject* CScore_Repr(CScore* Self)            //调用repr内置函数时自动调用.  
  38. {  
  39.        return CScore_Str(Self);  
  40. }  

 

可以看出,类的成员函数都是这样一个面孔:static void MethodName(ClassType* Self, …),如果函数有返回值的话,将void替换为Pyobject*即可。所有的函数名自己随便定。至于第一个参数Self,是必须有的,它就像Python中类方法定义时的self一样,和C++中的this指针的机理差不多。正如你所见,用它来引用类中的数据成员。

 

在构造方法中,我们通过解析参数,得到每一个成员的值。由于在构造方法中newm_szName的内存空间,所以在析构函数中释放它。如果你看过我的上一篇《用C++扩展Python的话,这几个函数理解起来应该很容易!

 

CScore类的外部方法:

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. ////////////////////////////////////////////////////////////  
  2. // CScore类的所有Get方法.  
  3. //  
  4. static PyObject* CScore_GetName(CScore* Self)  
  5. {  
  6.        return Py_BuildValue("s", Self->m_szName);  
  7. }  
  8.   
  9. static PyObject* CScore_GetMath(CScore* Self)  
  10. {  
  11.        return Py_BuildValue("f", Self->m_dMath);  
  12. }  
  13.   
  14. static PyObject* CScore_GetEnglish(CScore* Self)  
  15. {  
  16.        return Py_BuildValue("f", Self->m_dEnglish);  
  17. }  
  18.   
  19. ////////////////////////////////////////////////////////////  
  20. // CScore类的所有Set方法.  
  21. //  
  22. static PyObject* CScore_SetMath(CScore* Self, PyObject* Argvs)  
  23. {  
  24.        Py_INCREF(Py_None);  
  25.        if(!PyArg_ParseTuple(Argvs, "f", &Self->m_dMath))  
  26.        {  
  27.               cout<<"Parse the argument FAILED! You should pass correct values!"<<endl;  
  28.               return Py_None;  
  29.        }  
  30.   
  31.        return Py_None;  
  32. }  
  33.   
  34. static PyObject* CScore_SetEnglish(CScore* Self, PyObject* Argvs)  
  35. {  
  36.        Py_INCREF(Py_None);  
  37.        if(!PyArg_ParseTuple(Argvs, "f", &Self->m_dEnglish))  
  38.        {  
  39.               cout<<"Parse the argument FAILED! You should pass correct values!"<<endl  
  40.               return Py_None;  
  41.        }  
  42.   
  43.        return Py_None;  
  44. }  
  45.   
  46. static PyObject* CScore_PrintInfo(CScore* Self)  
  47. {  
  48.        cout<<"The scores as follows:"<<endl  
  49.            <<"=============================="<<endl  
  50.            <<"Name    : "<<Self->m_szName<<endl  
  51.            <<"Math    : "<<Self->m_dMath<<endl  
  52.            <<"English : "<<Self->m_dEnglish<<endl  
  53.            <<"=============================="<<endl;  
  54.   
  55.        Py_XINCREF(Py_None);  
  56.        return Py_None;  
  57. }  

 

都很简单,是不?

现在这个类有了GetSet方法,还可以很方便的调用PrintInfo输出所有信息。很好很强大。

 

 

5> 类的所有方法的列表说明:

 

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. static PyMethodDef CScore_MethodMembers[] =      //类的所有成员函数结构列表.  
  2. {  
  3.        {"GetName",    (PyCFunction)CScore_GetName, METH_NOARGS,     "Get the name of instance."},  
  4.        {"GetMath",    (PyCFunction)CScore_GetMath, METH_NOARGS,     "Get the math score of instance."},  
  5.        {"GetEnglish", (PyCFunction)CScore_GetEnglish, METH_NOARGS,  "Get the english score of isntance."},  
  6.   
  7.        {"SetMath",    (PyCFunction)CScore_SetMath, METH_VARARGS,    "Set the math score of instance."},  
  8.        {"SetEnglish", (PyCFunction)CScore_SetEnglish, METH_VARARGS, "Set the english of instance."},  
  9.   
  10.        {"PrintInfo",  (PyCFunction)CScore_PrintInfo, METH_NOARGS,   "Print all information of instance."},  
  11.   
  12.        {NULL, NULL, NULL, NULL}  
  13. };  
  

 

每一项都唯一的说明了一个类方法。下面解释之:

 依然是staticPyMethodDef定义了方法列表说明的结构。第一个字符串”GetName”——是Python中可见的那个函数名——依然不必与C++中所对应的函数名相同,可以自己定。

(PyCFunction)CScore_GetName——是C++中的对应的函数指针。

METH_VARARGS——定义了从PythonC++函数传递参数的方法。METH_VARARGS指定了用Tuple传递,如果没有参数,请用METH_NOARGS

一个字符串”Get the Name …Instance”——是这个函数的DocString,在Python中可以用MethodName.__doc__获得。

 

6> Yeah!类的各个部件——数据成员、方法成员都已说明完毕!下面就是将这些说明列表有机的组织起来。如下:

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. ////////////////////////////////////////////////////////////  
  2. // 类/结构的所有成员、内置属性的说明信息.  
  3. //  
  4. static PyTypeObject CScore_ClassInfo =  
  5. {  
  6.        PyVarObject_HEAD_INIT(NULL, 0)"Module.MyCppClass",                 //可以通过__class__获得这个字符串. CPP可以用类.__name__获取.  
  7.        sizeof(CScore),                 //类/结构的长度.调用PyObject_New时需要知道其大小.  
  8.        0,  
  9.        (destructor)CScore_Destruct,    //类的析构函数.  
  10.        0,  
  11.        0,  
  12.        0,  
  13.        0,  
  14.        (reprfunc)CScore_Repr,          //repr 内置函数调用。  
  15.         0,  
  16.        0,  
  17.        0,  
  18.        0,  
  19.        0,  
  20.        (reprfunc)CScore_Str,          //Str/print内置函数调用.  
  21.        0,  
  22.        0,  
  23.        0,  
  24.        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,                 //如果没有提供方法的话,为Py_TPFLAGS_DEFAULE  
  25.        "MyCppClass Objects---Extensioned by C++!",                   //__doc__,类/结构的DocString.  
  26.        0,  
  27.        0,  
  28.        0,  
  29.        0,  
  30.        0,  
  31.        0,  
  32.        CScore_MethodMembers,        //类的所有方法集合.  
  33.        CScore_DataMembers,          //类的所有数据成员集合.  
  34.        0,  
  35.        0,  
  36.        0,  
  37.        0,  
  38.        0,  
  39.        0,  
  40.        (initproc)CScore_init,      //类的构造函数.  
  41.        0,  
  42. };  

 

不知你此刻感觉如何,我当初是被吓着了!不过回头看一下,也挺简单,一一介绍之。

依然static!第一个字符串可以用ClassName.__name__获取,是类的内置名称。

sizeof(CScore)——说明了类/结构的大小。

0的不看了,其余的有注释,其中每个位置的对应项不可搞错。比如第4项是析构函数的函数指针,第2728项分别说明了类的所有数据、方法集合。35项是类的构造函数……

 

7> 一切对类的说明完毕,下来是对模块的说明:

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. ////////////////////////////////////////////////////////////  
  2. // 此模块的说明信息.  
  3. //  
  4. static PyModuleDef ModuleInfo =  
  5. {  
  6.        PyModuleDef_HEAD_INIT,  
  7.        "My C++ Class Module",               //模块的内置名--__name__.  
  8.        "This Module Created By C++--extension a class to Python!",                 //模块的DocString.__doc__  
  9.        -1,  
  10.        NULL, NULL, NULL, NULL, NULL  
  11. };  

 

依然static!第一项总为PyModuleDef_HEAD_INIT

模块的内置名,可以用ModuleName.__name__获取。

模块的DocString,可以用ModuleName.__doc__获取。

 

8> 好了!终于到了导出这一步了。导出函数的名字和模块的名字息息相关。

   基本上有:模块初始函数名 = PyInit_ + 模块文件名这是Python 3.1中的方法,2.x中可能有出入!

 

view plaincopy to clipboardprint?·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. ////////////////////////////////////////////////////////////  
  2. // 模块的初始化函数. import 时自动调用.  
  3. //  
  4. PyMODINIT_FUNC           // == __decslpec(dllexport) PyObject*, 定义导出函数.  
  5. PyInit_Score(void)       //模块外部名称为--CppClass  
  6. {  
  7.        PyObject* pReturn = 0;  
  8.        CScore_ClassInfo.tp_new = PyType_GenericNew;       //此类的new内置函数—建立对象.  
  9.   
  10.       /////////////////////////////////////////////////////  
  11.       // 完成对象类型的初始化—包括添加其继承特性等等。  
  12.       // 如果成功,则返回0,否则返回-1并抛出异常.  
  13.       //  
  14.   
  15.        if(PyType_Ready(&CScore_ClassInfo) < 0)  
  16.               return NULL;  
  17.   
  18.        pReturn = PyModule_Create(&ModuleInfo);          //根据模块信息创建模块.  
  19.        if(pReturn == 0)  
  20.               return NULL;  
  21.   
  22.        Py_INCREF(&ModuleInfo);  
  23.        PyModule_AddObject(pReturn, "CScore", (PyObject*)&CScore_ClassInfo); //将这个类加入到模块的Dictionary中.  
  24.        return pReturn;  
  25. }  

 

PyType_Ready 函数,根据参数——导出类的说明信息——来判断类是否可导出。

PyModule_Create创建这个模块。

PyModule_AddObject将这个类加入到模块中!请注意:第一个参数为PyObject*模块对象,第二个为导出类在Python Intepreter中的名字——这个字符串依然可以和C++中所对应的类名不同!

 

三、测试导出模块:

 

激动人心的时候到了!我们的类究竟是否在Python中可用?下面就是——见证奇迹的时刻!

1>    我们很熟练的设置Win32 DLL Project生成的文件为Score.pyd——因为我们的初始函数为PyInit_Score!如果你还不知道如何设置,请参考我的《用C++扩展Python一文。

2>    然后组建即可。在Release目录下,不出意外的话可以找到一个120KBScore.pyd文件。我们可以将它复制到D:/下,然后cmdcd切换到D:/下,键入Python,得到如下界面:

 

C++导出类到Python 

3>    然后可以随心所欲的进行操作了,比如我们键入下列命令:

>>>import Score

>>>dir(Score)

>>>Score.__doc__

>>>Score.__name__

>>>Score.__file__

可以依次得到模块ScoreDocString、内置模块名、模块所对应的文件名……运行结果如下:

 C++导出类到Python

 

 

4>    有待探索的东西还很多:

>>>from Score import *                   #导入模块中所有方法
>>>dir(CScore)                                 #查看类CScore的所有内置属性
>>>CScore.__doc__
>>>CScore.GetEnglish.__doc__
>>>CScore.GetMath.__name__
>>>CScore.GetName 

 

可以看到,输出的信息和我们所定义的是一致的!有一点成就感了吧?

 C++导出类到Python

 

 

      

5>    下面是重中之重——我们的类到底可不可用呢?试试先:

>>>StuScore = CScore(“Kobe Bryant”, 88, 100)

>>>StuScore.GetName()

>>>StuScore.GetMath()

>>>StuScore.GetEnglish()

>>>StuScore.PrintInfo()

 C++导出类到Python

 

 
 

这一幕终于出现了!!!我们看到,我们的类的确起了作用,并且工作的很好!不是吗?我们可以获得他的各种信息,也可以将其信息打包输出。当然,还可以更改:

>>>StuScore.SetMath(90.5)

>>>StuScore.SetEnglish(95)

>>>print(StuScore)

一切皆在预料之中!我们甚至可以用Python中内置的print函数输出这个类!!!请不要惊慌,我们的print函数只是简单的调用了print(str(StuScore))这个函数而已!而对内置函数str的定义是我们在C++程序中所作了的——还记得那一长串令人头痛的类信息说明结构吗??

C++导出类到Python
 

 

我们接着来:

>>>repr(StuScore)

>>>str(StuScore)

>>>print(str(StuScore))

 

C++导出类到Python
 

 

依然正常工作!

我们甚至可以在类对象之间相互赋值,删除对象……

>>>Another = StuScore

>>>print(Another)

>>>del StuScore

 

 C++导出类到Python
 

 

del时,对象自动调用C++中所定义的那个析构函数来释放所占用的内存!

 

6>    这个模块中还有一些东西有待挖掘,我想,还是留给您去探索吧——这样才会了解的深入。

 

通过这个例子我们可以看出,我们可以很方便的用C++写的类来扩展Python库!也许你不认为这是个好办法——Python自身写的类就可以啊,为什么非要用C++写的呢?但请不要忘记——在对系统处理速度要求较高的一些软件(或程序)中,可以很方便的利用C/C++的运行速度快的特点,来对Python进行扩展。

 

7>    呃,就这样完了?不会!如果你对空间大小要求较高的话,可以看到——就这么几行的一个类,却要占用120KB的空间,如果是一个几百行的类呢?一些类呢?下面介绍一种用Python Script来生成.pyd文件的方法。准备好了吗?Go

 

我们新建一个Python脚本——CreatePyd.py。然后进行如下编辑:

     

view plaincopy to clipboardprint?
  1. from distutils.core import setup, Extension  
  2.   
  3.   
  4. ModuleInfo = Extension("Score", sources = [r"Extension_Class.cpp"])  
  5.   
  6. setup(name             = "Score",  
  7.       version          = "1.0",  
  8.       description      = "This module created by C++ YaFeng.Zhang",  
  9.       author           = 'YaFeng.Zhang',  
  10.       author_email     = 'zyfgood12@163.com',  
  11.       license          = "You can copy this program to anywhere.",  
  12.       url              = "http://blog.csdn.net/ZhangYafengCPP",  
  13.       long_description = '''''This is really just a demo!''',  
  14.       platforms        = "AMD5600+ WinXP",  
  15.       ext_modules      = [ModuleInfo]  
  16.      )  
                               

 

 

我们大致解释下:先导入所需的Python模块。然后用Extension函数关联一个Cpp源文件(就是我们刚刚写的那个文件)和一个要生成的模块名——注意:没有.pyd后缀。

然后调用setup函数生成一个名字nameScore的模块,版本version1.0,描述description,作者信息author,作者邮箱author_email,还其平台platforms等等他有用的信息!

 

比如我们将这个CreateDLL.pyExtension_Class.cpp拷贝到D:/下,依然切换到D:/目录下,键入”python CreateDLL.py build”

 

 C++导出类到Python

 

 

经过生成后,就会新生成一个build目录,在build/lib.win32-3.1下你可以找到Score.pyd这个文件。但它只有12KB!比用VC生成的整整小了10倍!可以试一下,它依然能正常工作。

 

8>    模块的安装:

也许你对你生成的模块比较满意,那么你可以将其拷入Pythonlib目录下,以后不用切换盘符就可以方便的调用。但还有一种更为简便的方法:用命令行键入: Python CreateDLL.py install:

 

 C++导出类到Python

 

 

也许你的显示和我不同——因为我们的Python不一定安装在同一个目录下。但结果是我们在Python安装目录下的lib/site-packates下可以找到刚刚生成的.pyd模块和一个模块信息说明文件,我们用记事本打开之:

 

C++导出类到Python
 

 

其中恰恰是我们的py脚本中给setup函数所传递的那些参数!

 

四、小结:

         好了,就这些。我们通过“创建结构--> 创建方法--> 数据成员说明-->方法说明--> 关联结构和数据、方法--> 模块说明--> 生成模块”这一系列犀利的操作成功的从C++中向Python导出了一个类!

      

  还简单的介绍了如何用Python生成模块、以及模块的简便安装等等!好了,就这些吧!结合上一篇《用C++扩展Python和这篇《C++导出类到Python》,我已经基本上将如何用C++扩展Python模块将述了一遍。希望对你有所帮助,至少对于我而言,是总结了我的所学!

 

 如果你有什么建议或想法,请与我联系,让我们共同进步!我的联系方式:

                               

                              Blog:    http://blog.csdn.net/ZhangYafengCPP

                                                                                                                   Email:  zyfgood12@163.com

                                                                                                                   QQ:     798446303

                                                                                                                   --By:    Ac_1ZYF-C++