Chinaunix首页 | 论坛 | 博客
  • 博客访问: 381329
  • 博文数量: 73
  • 博客积分: 3574
  • 博客等级: 中校
  • 技术积分: 1503
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-26 11:17
文章分类

全部博文(73)

文章存档

2012年(14)

2011年(15)

2010年(44)

分类: Python/Ruby

2012-08-14 23:47:26

    在学习python之前,一直被告知python可以作为一种“胶水语言”用来方便的连接其他语言的程序,但知道今天才亲自实现了python的C扩展。

    细节先不说,说下自己的感受吧,感觉python在C扩展上并没有很强大的优势,跟lua比貌似要差一些,回头再实验对比下。

    为python创建扩展需要三个主要的步骤:
    1、创建应用程序代码
    2、利用样板来包装代码
    3、编译与测试

1、创建应用程序代码
   比如,测试如下两个功能的函数,一个是递归求阶乘的函数fact(),另一个是reverse()函数实现字符串反转算法。

点击(此处)折叠或打开

  1. int fact(int n)
  2. {
  3.     if (n < 2)
  4.         return 1;
  5.     
  6.     return (n) * fact(n - 1);
  7. }

  8. char *reverse(char *s)
  9. {
  10.     /*目的是修改传入的字符串,使其内容完全反转,但不需要申请内存*/
  11.     register char t,*p = s,*q = (s + strlen(s) - 1);

  12.     while(p < q)
  13.     {
  14.         t = *p;
  15.         *p++ = *q;
  16.         *q-- = t;
  17.     }

  18.     return s;
  19. }

2、用样板包装代码
    样板主要分为4步:
    1)包含python的头文件
       首先,需要找到python的头文件在哪,并且确保编译器有权访问它们,Python.h,在测试设备上,此文件位于/usr/include/python2.*/目录,并非网上流传的/usr/local/include/python2.*目录。
       在代码里加入  #include   //注意大小写

    2)为每一个模块的每一个函数增加一个型如PyObject *Module_func()的包装函数
       为所有想被python环境访问的函数都增加一个静态函数,函数的返回值为PyObject *,函数名前面要加上模块名和一个下划线。比如在python需要import扩展的fact()函数,其所在的模块名为Extest,那么就要创建一个包装函数为Extest_fact()。

点击(此处)折叠或打开

  1. static PyObject * Extest_fac(PyObject *self,PyObject *args)
  2. {
  3.     int num;
  4.     /*从python到C的参数转换,通过PyArg_Parse*系列函数,用法跟c的sscanf函数相似,
  5.       接收字符流,根据指定的格式进行解析*/
  6.     if(!PyArg_ParseTuple(args,"i",&num))
  7.         return NULL;
  8.     /*从C到python的转换,通过Py_BuildValue函数,用法类似c的sprintf函数*/
  9.     return (PyObject *)Py_BuildValue("i",fact(num));
  10. }
  11. /*在这里修改了resver函数的含义,使得其同时放回原字符串和反转字符串*/
  12. static PyObject *Extest_resver(PyObject *self,PyObject *args)
  13. {
  14.     char *orig_str;
  15.     char *dup_str;
  16.     PyObject *ret;

  17.     if(!PyArg_ParseTuple(args,"s",&orig_str))
  18.         return NULL;
  19.     
  20.     /*这里需要注意,在调用reverse函数的时候进行了内存copy,需要手动释放内存,避免内存泄露*/
  21.     ret = (PyObject *)Py_BuildValue("ss",orig_str,\
  22.             dup_str = reverse(strdup(orig_str)));
  23.     free(dup_str);
  24.     
  25.     return ret;
  26. }
    3)为每一个模块增加一个型如PyMethodDef ModuleMethonds[]的数组
       完成包装函数后,需要把它们列在某个地方,以便python解释器能够导入并调用它们,这就是ModuleMethods[]数组要做的事情。
       为Extest模块创建ExtestMethods[]数组:

点击(此处)折叠或打开

  1. static PyMethodDef
  2. ExtestMethods[] = {
  3.     {"fact",Extest_fac,METH_VARARGS},
  4.     {"resver",Extest_resver,METH_VARARGS},
  5.     {NULL,NULL},
  6. };
    4)增加模块初始化函数void initModule()
       最后一部分就是模块的初始化函数,这部分代码在模块被导入的时候被解释器调用。这里需要调用Py_InitModule()函数,并把模块名和ModuleMethods[]数组的名字传递进去。

点击(此处)折叠或打开

  1. void initExtest() {
  2.     Py_InitModule("Extest",ExtestMethods);
  3. }

3、编译
   python中distutils包被用来编译,安装和分发这些扩展模块,步骤如下:
   1)创建setup.py
      为了能够编译扩展,需要为每一个扩展创建一个Extension实例,Extension(MOD,sources=[*.c]),第一个参数是扩展的名字,sources参数是所有源代码的文件列表。
      接下来,可以条用setup()了,setup需要两个参数:一个名字参数表示要编译哪个东西,一个列表列出要编译的对象。

点击(此处)折叠或打开

  1. #!/usr/bin/python

  2. from distutils.core import setup,Extension

  3. MOD='Extest'
  4. 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。
       接下来就可以通过解释器测试新加入的模块了

点击(此处)折叠或打开

  1. >>> import Extest
  2. >>> dir(Extest)
  3. ['__doc__', '__file__', '__name__', '__package__', 'fact', 'resver']
  4. >>> Extest.fact(3)
  5. 6
  6. >>> Extest.resver('hello')
  7. ('hello', 'olleh')
  8. >>>

至此,python的C扩展就全部结束了,以后会抽空测试以下更复杂的情况以及与lua扩展的对比。
阅读(2208) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~