用c写python的拓展模块
需要为python程序写C拓展模块的情况
- 写python不具有的功能
- 复杂算法提高程序的性能
- 隐藏核心算法的代码
以下是测试的开发环境
- Linux version 2.6.32-358.el6.i686
- Python 2.6.6 (r266:84292, Oct 12 2012, 14:36:13)
- gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)
利用C的API写python的拓展模块
首先编写extest1.c文件,它实现了两个功能强大的函数,计算整数阶乘和字符串反转。如下:
/**************************************************
File name: extest1.c
Author:
mail:
Create Time: 2016-12-06 11:38
Copyright 2016 , all rights reserved.
****************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "extest1.h"
int fac (int n)
{
if (n<2)
{
return 1;
}
else if (n>=2)
return n*fac (n-1);
}
char *reverse( char *s )
{
char *p,*q;
p=s;
q=s+(strlen(s)-1);
char tmp;
while (p < q)
{
tmp = *p;
*p++ = *q;
*q-- = tmp;
}
return s;
}
为了方便别的文件引用这两个函数,给它写个头文件extest1.h,让别的文件方便引用,如下:
int fac (int n);
char *reverse( char *s );
函数的功能已经完成了,我们再写个主函数的文件mian.c测试一下:
/**************************************************
> File name: main.c
> Author:
> mail:
> Create Time: 2016-12-09 17:08
> Copyright 2016 , all rights reserved.
****************************************************/
#include <stdio.h>
#include <string.h>
#include "extest1.h"
int main(int argc, char* argv[])
{
int n=0;
char s[20];
char t[20];
printf("input a interger:\n");
scanf("%d",&n);
getchar();//get the enter
printf("%d 的阶乘是 %d\n",n,fac(n));
printf("input a str ,which's length less than 19 : \n");
scanf("%s",&s);
strncpy(t,s,19);
printf("%s 的字符串反转是 %s \n",s,reverse(t));
return 0;
}
文件写好了之后编译它。
gcc -o a.out extest1.c main.c
执行a.out结果如下:
[root@localhost candpy]# ./a.out
input a interger:
4
4 的阶乘是 24
input a str ,which's length less than 19 :
helloworld!
helloworld! 的字符串反转是 !dlrowolleh
好了,C的功能强大的模块已经写好了,现在开始把它变成python的一个拓展。
Python是c语言编写的,它可以直接读C形成的动态库。但是,对于C的数据类型、函数类型,Python并不认识,不能直接用,需要进行一次封装,让他们变成Python识别的类型。其中关于Python数据类型,在C语言中的声明在头文件Python.h。
例如,要对上面的函数reverse 创建一个python的封装函数。
首先,必须创建一个以static PyObject* 的类型的函数。 封装函数的名字最好以Module_func() 形式命名,Module是想创建的python模块名,func是函数名。(这个名称我测试了一下,可以不以这种方式命名,程序是识别的时候不是通过这里的命名规则关联,只是这样规范一点。)
现在,该函数就像其他的python模块中的函数一样,通过Module.func()的形式调用了。
以第一个函数fac为例,名称如下:
static PyObject * Extest_fac ( PyObject *self,PyObject *args )
其中的输入参数第一个*self是默认的,不用管。第二个参数args对应原来函数fac中的输入参数,是python格式的。
在函数中需要转换成c格式的,调用C模块中相应的函数fac,再将输出的C格式转换成Python返回。
完整函数如下:
//#include "/usr/include/python2.6/Python.h"
#include "Python.h"
#include <stdio.h>
#include "extest1.h"
static PyObject * Extest_fac ( PyObject *self,PyObject *args ) {
int num;
if (!PyArg_ParseTuple (args,"i",&num)) return NULL;
return ((PyObject*) Py_BuildValue( "i" , fac(num) )) ;
}
//输出原始字符和一个反转字符的二元组。
static PyObject *Extdoppel ( PyObject *self , PyObject * args ){
char *orig_str,*copy_str;
if (!PyArg_ParseTuple(args,"s",&orig_str) ) return NULL;
PyObject* retval;
copy_str = strdup(orig_str);
retval = (PyObject*) Py_BuildValue("ss",orig_str, reverse(copy_str));
free(copy_str);
return (retval);
}
static PyMethodDef ExtestMethods[] = {
{ "fac",Extest_fac,METH_VARARGS },
{ "doppe",Extdoppel,METH_VARARGS },
{ NULL,NULL },
};
void initExtest() {
Py_InitModule("Extest",ExtestMethods);
}
当执行import Extest的时候,首先初始化initExtest
再关联到ExtestMethods,
其中的数组把函数Extest_fac关联到fac,Extdoppel关联到doppe。
好了,现在所有的源文件已经准备好了。
我们把它编译成动态库。
gcc -fPIC -shared extest1.c pytest1.c -o Extest.so -I /usr/include/python2.6/
其中的I 后面的路径是你Python.h头文件的路径。
进入python环境。
Extest.so文件中包含的Extest模块,现在可以像普通的python模块一样被引用了。
>>> from Extest import fac
>>> from Extest import doppe
>>> fac(4)
24
>>> doppe('hello world')
('hello world', 'dlrow olleh')
>>>
但是如果工程比较复杂,直接用命令敲的方式就比较麻烦。工程管理可以使用Makefile。python也提供更简单的方式setup的方式。
setup.py文件内容如下:
#!/usr/bin/env python
from distutils.core import setup,Extension
setup(
name = 'Extest',
version='1.0',
ext_modules = [Extension('Extest',sources=['pytest1.c','extest1.c'],include_dirs=['/usr/include/python2.6/'])]
)
建立模块的包:
python setup.py build
就可以生成工程文件的目录了
python setup.py install
可以把库文件安装进python的库文件目录中 ,通过python,在任何目录下都可以使用该模块了。
使用SWIG为C自动生成python的拓展
以上介绍的是利用C语言的python API为python写拓展模块的方式,那有没有更简单的方式呢。SWIG可以自动为C语言的函数生成其他语言的拓展。首先安装swig工具(略)。
再编写如下文件extest1.i
/* example.i */
%module Extest //模块名
%{
#include "extest1.h"
%}
int fac (int n);
char *reverse( char *s );
用swig工具根据example.i生成extest1.c的python接口文件。
swig -python extest.i
即可生成extest1_wrap.c(extest1.c的接口文件,名字根据extest1 而来)、Extest.py(名字根据文件里面的module 而来)。
在编译成动态库:
gcc -fPIC -shared extest1.c extest1_wrap.c -o _Extest.so -I /usr/include/python2.6/
生成目标动态库文件 _Extest.so。
注意, 这里的 _Extest.so 名称是固定的。命名规则是:前面的下划线(python拓展模块的标准写法)+模块名。
也可以使用
python setup.py install
方式安装模块。setup.py内容如下。
#!/usr/bin/env python
from distutils.core import setup,Extension
setup(
name = 'Extest',
version='1.0',
ext_modules = [Extension('_Extest',sources=['extest1_wrap.c','extest1.c'],include_dirs=['/usr/include/python2.6/'])]
)
现在,swig生成的动态库和自己手写的一样可以工作了。
只要C写好了函数,用swig自动就能生成python的模块了,这是不是简单一些呢?
但是,如果C模块中的参数是复杂类型,如结构体等,则需要特别注意一下,这块内容以后有时间在深入了解。