在学习python之前,一直被告知python可以作为一种“胶水语言”用来方便的连接其他语言的程序,但知道今天才亲自实现了python的C扩展。
细节先不说,说下自己的感受吧,感觉python在C扩展上并没有很强大的优势,跟lua比貌似要差一些,回头再实验对比下。
为python创建扩展需要三个主要的步骤:
1、创建应用程序代码
2、利用样板来包装代码
3、编译与测试
1、创建应用程序代码
比如,测试如下两个功能的函数,一个是递归求阶乘的函数fact(),另一个是reverse()函数实现字符串反转算法。
- int fact(int n)
- {
- if (n < 2)
- return 1;
-
- return (n) * fact(n - 1);
- }
- char *reverse(char *s)
- {
- /*目的是修改传入的字符串,使其内容完全反转,但不需要申请内存*/
- register char t,*p = s,*q = (s + strlen(s) - 1);
- while(p < q)
- {
- t = *p;
- *p++ = *q;
- *q-- = t;
- }
- return s;
- }
2、用样板包装代码
样板主要分为4步:
1)包含python的头文件
首先,需要找到python的头文件在哪,并且确保编译器有权访问它们,Python.h,在测试设备上,此文件位于/usr/include/python2.*/目录,并非网上流传的/usr/local/include/python2.*目录。
2)为每一个模块的每一个函数增加一个型如PyObject *Module_func()的包装函数
为所有想被python环境访问的函数都增加一个静态函数,函数的返回值为PyObject *,函数名前面要加上模块名和一个下划线。比如在python需要import扩展的fact()函数,其所在的模块名为Extest,那么就要创建一个包装函数为Extest_fact()。
- static PyObject * Extest_fac(PyObject *self,PyObject *args)
- {
- int num;
- /*从python到C的参数转换,通过PyArg_Parse*系列函数,用法跟c的sscanf函数相似,
- 接收字符流,根据指定的格式进行解析*/
- if(!PyArg_ParseTuple(args,"i",&num))
- return NULL;
- /*从C到python的转换,通过Py_BuildValue函数,用法类似c的sprintf函数*/
- return (PyObject *)Py_BuildValue("i",fact(num));
- }
- /*在这里修改了resver函数的含义,使得其同时放回原字符串和反转字符串*/
- static PyObject *Extest_resver(PyObject *self,PyObject *args)
- {
- char *orig_str;
- char *dup_str;
- PyObject *ret;
- if(!PyArg_ParseTuple(args,"s",&orig_str))
- return NULL;
-
- /*这里需要注意,在调用reverse函数的时候进行了内存copy,需要手动释放内存,避免内存泄露*/
- ret = (PyObject *)Py_BuildValue("ss",orig_str,\
- dup_str = reverse(strdup(orig_str)));
- free(dup_str);
-
- return ret;
- }
3)为每一个模块增加一个型如PyMethodDef ModuleMethonds[]的数组
完成包装函数后,需要把它们列在某个地方,以便python解释器能够导入并调用它们,这就是ModuleMethods[]数组要做的事情。
为Extest模块创建ExtestMethods[]数组:
- static PyMethodDef
- ExtestMethods[] = {
- {"fact",Extest_fac,METH_VARARGS},
- {"resver",Extest_resver,METH_VARARGS},
- {NULL,NULL},
- };
4)增加模块初始化函数void initModule()
最后一部分就是模块的初始化函数,这部分代码在模块被导入的时候被解释器调用。这里需要调用Py_InitModule()函数,并把模块名和ModuleMethods[]数组的名字传递进去。
- void initExtest() {
- Py_InitModule("Extest",ExtestMethods);
- }
3、编译
python中distutils包被用来编译,安装和分发这些扩展模块,步骤如下:
1)创建setup.py
为了能够编译扩展,需要为每一个扩展创建一个Extension实例,Extension(MOD,sources=[*.c]),第一个参数是扩展的名字,sources参数是所有源代码的文件列表。
接下来,可以条用setup()了,setup需要两个参数:一个名字参数表示要编译哪个东西,一个列表列出要编译的对象。
- #!/usr/bin/python
- from distutils.core import setup,Extension
- MOD='Extest'
- setup(name=MOD,ext_modules=[Extension(MOD,sources=['fact.c'])])
2)通过运行setup.py来编译和连接代码
通过setup.py build命令开始编译扩展模块,如果编译成功的话,会再运行setup.py脚本所在目录下的build/lib.*目录中
3)从Python中导入模块,测试功能
可以直接切换到上文提到的目录中来测试模块,或者也可以将这些模块安装到全局的python模块中:python setup.py install。
接下来就可以通过解释器测试新加入的模块了
- >>> import Extest
- >>> dir(Extest)
- ['__doc__', '__file__', '__name__', '__package__', 'fact', 'resver']
- >>> Extest.fact(3)
- 6
- >>> Extest.resver('hello')
- ('hello', 'olleh')
- >>>
至此,python的C扩展就全部结束了,以后会抽空测试以下更复杂的情况以及与lua扩展的对比。
阅读(2208) | 评论(0) | 转发(0) |