C++扩展Python

 

                                                                                                                                                                       点击下载源码        

      寒假学了下Python,原是想用来做游戏脚本开发的。自然而然就接触到了Python与C++的扩展与嵌入的问题。网上搜了好久,搜到的资料都是3.x之前的。由于我用的Python Interpreter的版本是3.1,而3.1在语法、结构等等上都做了不同程度的修改——比如去掉了print语句,用print函数替代等等,以致于那些例子在3.1的Python API/C下不能编译所以自己摸索了一下,总结一些跟大家分享。

       究其根源,是一些API/C函数发生了变化而引起的。这两天翻了下Python文档,也有了一些想法,所以写出来,与大家共享一下。今天谈谈如何用C++扩展Python——我用的是Python API/C,而不是 Boost.Python!

       对Python进行扩展——Extension,就是让Python能调用用C写的函数等等,这样便可以很轻松的对Python库进行扩展。如果你熟悉C++/C编程,并且熟悉Python操作的话,看个例子先:

1>    我们熟练的打开VC6.0或VS2008,然后File-->New-->Project-->Win32 DLL Project。比如我们输一个AddTwoValue吧,建立一个Empty Project,一路确定。

当然,你的Python是安装在电脑里的,并且你的VC的目录中分别添加了../Include 和 ../libs的。如果没有的话,请添加。

2>    添加一个C++或C源文件。我添加的是C++文件:AddTwoValue.cpp。打开。

3>    输入如下代码:

 

①这段程序应该不难。我们知道,在Python中,“万物皆对象”,PyObject就是Python中对象在C++中的表现形式——无论是List,还是Tuple,抑或是Dictionary、String,在用C++扩展时,都用PyObject表示。

②再看其“参数”,第一个参数Self和在Python中定义类成员函数时用的Self相似——只要写上就行了。第二个就是其真正的参数了。

③呃,这个PyArg_ParseTuple有点难看,先按字面意思理解:Arg—Argument,参数,Parse—解析,Tuple—就是Python中的Tuple了,也有人叫做“元组”。总的来说,这个函数就是将PyObject*的参数对象解析。如果你还知道Py_BuildValue的话,PyArg_ParseTuple就是Py_BuildValue的反向操作。将Argvs按字符串(“ii”)的格式分别解析存放到两个int型变量中。如果解析失败,则返回0。

④cout这句不解释!!我们的函数只是要输出两个数的和,并不会返回什么东东,所以返回Py_None。None——呃,与Python中的None值一样——只不过Py_None是None在C++中的表现方式。

⑤等等!Py_INCREF是搞么子的?看一下,INC—Increase,增加,REF—Reference,引用。也就是增加PyObject对象的引用计数。

  Py_DECREF—与Py_INCREF相反,只是减小PyObject对象的引用计数。

⑥我们已经知道:在Python中,Python是自动分配、回收内存的,但是C++中不是这样——也许你在这儿分配了一段内存,却要一直担心没有释放它——反正我是这样的。所以,为了让Python回收废弃的内存、或者防止Python过早地自动回收内存,我们必须用Py_EDCREF和Py_INCREF来控制PyObject的引用计数。

 

4>    好了,上面是我们的主要的导出函数。下面就是对这个函数的说明了——总得让Python知道我们的模块中有什么吧!接下来看:

 

这个结构就是对模块中的所有方法、属性的说明列表。因为我们的这个模块中只有一个AddTwoInteger函数,所以只有一项。下面仔细看看各个参数:

①"Add"—这个字符串是我们最终导出的函数名,可以在Python中调用。正如你所见,这个字符串不一定非要和我们C++中定义的函数名AddTwoInteger相同。

②AddTwoInteger—这便是我们的C++中定义的导出函数的函数名了。

③METH_VARARGS—这规定了Python向这个C++函数传递参数的方式。如果是METH_VARARGS,则用Tuple来传递;如果是METH_KEYWORDS,则用Dictionary的Key(键值)来传递参数。

④"Add Two Integers!"—这句仍然简单。也就是AddTwoInteger函数的说明字符串。在Python中就是函数的DocString __doc__。

5>    好!对模块中所有函数、属性的说明完了。下面对模块进行说明——总得让Python知道你的模块的组织形式等等吧。For Example:

仍然是static:

①第一项,在Python的文档中是这样说的:“Always initialize this member to PyModuleDef_HEAD_INIT.”也就是说,第一项总是赋为PyModlueDef_HEAD_INIT。

②” AddTwoValue_Module”—这就是内置的模块名了,在Python中导入后,可以用模块名.__name__来获取这个字符串。注意:这个字符串和import Module这儿的Module没有关系!!也就是说,这儿的这个字符串并不一定是你的模块名!!

③”This …..two Integers!”—这句字符串和Function的__doc__一样,是模块的DocString,也可以用模块名.__doc__获得。

④-1——Python的文档中是这样说的:“size of per-interpreter state of the module,or -1 if the module keeps state in global variables.”意思是:置为-1的话,你的模块在全局范围。
⑤ModulesMethods—很眼熟,是不?仔细一想,呃,是先前对模块中所有函数方法、属性进行描述的那个列表!
注:这一步在2.x中是不必定义的。

 

6>  终于到最后一项了!如果你仔细的话,你注意到前面所定义的都有一个static修饰符。接下来的这个是唯一一个没有static修饰符的函数:

①好吧,PyMODINIT_FUNC:


可以看出,它定义了一个返回值为PyObject*的DLL导出函数。
②注意这个函数名:PyInit_xxx。在3.1中这个函数名必须为PyInit_ModuleName。而ModuleName必须和你最后生成的.pyd文件的名字一样!这是为什么呢?因为在你 import ModuleName时,Python会自动根据这个ModuleName推导出:PyInit_ModuleName这个初始化模块函数并执行之!如果你更改了.pyd文件名的话,就会导入模块失败!切记切记!!!!
   注:在Python2.x版本中,这个初始化函数为InitModuleName。但是Python3.1中不是这样的。
③然后是一个函数调用:PyModule_Create。其参数依然很熟悉——是描述模块信息的那个列表。这便初始化了模块。
  注:在Python2.x中,是用Py_InitModule来初始化的。我就着了这个道!!!找了半天Py_InitModule,楞是没找到。

7>  上面的代码看的差不多了吧?VC目录设置好了吧?Python环境变量也合适吧?下面我们来生成。默认生成的是.dll文件,但如上文所述,我们要的是.pyd文件。VC6.0中依然很熟练的Project-->Setting-->Link-->Output File Name-->更改为ModuleName.pyd——本例中是AddTwoIntegers-->OK。


VS2008中:Project-->Project属性-->连接器-->常规-->输出文件-->ModuleName.pyd-->OK。

本例中我们仍然是AddTwoIntegers.pyd。

 

 


8>    万事俱备,只欠组建。然后组建即可。不出意料的话,你会在Release/Debug下找到ModuleName.pyd文件,约莫100来k。这个文件千万不能重命名,否则Python导入时会出错!

 

9>    好了,该用用Python了!比如我们将Print.Pyd拷贝到D:/下,cmd中cd到D:/目录下,键入Python,出现如下界面(我的Python是3.1.1版本的):然后执行一系列操作(Python对大小写敏感):

运行效果如下:


 

我们可以知道,以上信息和我们C++文件中所写的各种信息一一对应。下面就是重头戏——在Python Interpreter中调用C++函数。我们很熟练的键入:

 


10>OK,一切正常!就是这么简单。如果你够有创意,你可以多写几个常用的函数,放到Python安装目录的lib下,以后你可以不必切换盘符,直接在你的工作中进行调用了。

   

      这篇教程就写到这儿了。也想写一篇从C++中导出类到Python的,不知道有没有机会——这是以后的事了!此贴可以任意复制传播,但请保留其完整性!谢谢!
      如果你发现错误,或者有疑惑或建议,都可以和我联系,我们共同交流进步。

更多推荐

用C++扩展Python