22.1.1 什么是扩展

         扩展:所有能被整合或导入到其他python脚本的代码,都可以称为扩展。e.g. 纯python、C/C++、Java、C#或Visual Basic.NET都可以

         22.1.2 为什么要扩展python

  • 添加/额外的(非python)功能
  • 性能瓶颈的效率提升——把软件开发过程中的瓶颈部分在扩展中实现
  • 保持专有源代码      
         22.2 创建python扩展(以C为例)
         一般过程:
  1. 创建应用程序;
  2. 利用样板来包装代码;
  3. 编译与测试。 
         22.2.1 创建应用程序
         即创建在Python内运行的一个模块。在本步骤中,最好尽可能完善代码,否则不利于查找bug。
         Extest1.c
[plain] view plain copy
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<string.h>  
  4.   
  5. int fac(int n)  
  6. {  
  7.     if (n < 2) return (1); /* 0! == 1! == 1 */  
  8.     return (n) * fac(n-1); /* n! == n * (n-1)!*/  
  9. }  
  10.   
  11. char *reverse(char *s)  
  12. {  
  13.     register char t,  
  14.         *p = s,  
  15.         *q = (s + (strlen(s)-1));  
  16.   
  17.     while (p < q)  
  18.     {  
  19.         t = *p;  
  20.         *p++ = *q;  
  21.         *q-- = t;  
  22.     }  
  23.     return s;  
  24. }  
  25.   
  26. int main()  
  27. {  
  28.     char s[BUFSIZ];  
  29.     printf("4! == %d\n", fac(4));  
  30.     printf("8! == %d\n", fac(8));  
  31.     printf("12! == %d\n", fac(12));  
  32.     strcpy(s, "abcdef");  
  33.     printf("reversing 'abcdef', we get '%s'\n", reverse(s));  
  34.     strcpy(s, "madam");  
  35.     printf("reversing 'madam', we get '%s'\n", reverse(s));  
  36.     return 0;  
  37. }  
        22.2.2 用样板来包装代码
        样板代码:接口的代码被称为样板代码。
        主要分为四个步骤:
  1. 包含Python的头文件;
  2. 为每一个模块的每个函数增加一个刑如PyObject* Module_func()的包装函数;
  3. 为每个模块增加一个形如PyMethodDef ModuleMethods[]的数组;
  4. 增加模块初始化函数void initModule()       
       1. 包含Python头文件
       在上述C代码中增加一行:(其他因为用的不是Unix系统,不太懂)
[plain] view plain copy
  1. #include "Python.h"  
       2. 为每一个模块的每个函数增加一个刑如PyObject* Module_func()的包装函数
       需要为所有想被Python环境访问的函数增加一个静态函数(?),函数返回类型是PyObject*,函数名前要加上模块名和一个下划线。
      从后面来看就是,与普通的Python模块导入没有什么差别,即在Python解释器中能先import Extest,再通过Extest.fac()来访问上述C代码中的fac()函数。
      包装函数的用处就是,先把Python的值传给C,然后调用相关函数,得到相应的结果,再把这个结果转换成Python对象,传回给Python。从Python到C,要用到PyArg_Parse*()函数,从C到Python要用到Py_BuildValue()函数。
      PyArg_Parse*()函数接受一个字符串流,并根据一个指定的格式字符串进行解析,把结果放入到相应的指针所指的变量中。若返回1,表示解析成功,返回0,表示解析失败。
       Py_BuildValue(),把所有的参数按格式字符串所指定的格式转换成一个Python对象。
函数 描述
int PyArg_ParseTuple() 把Python传过来的参数转为C
int PyArg_ParseTupleAndKeyWords() 同上,但同时解析关键字参数
Py_BuildValue() 把C的数据转为Python的一个或一组对象,然后返回
 
格式代码 Python型 C/C++型
s str char*
z str/None char*/NULL
i int int
l long long
c str char
d float double
D complex Py_Complex*
O (any) PyObject*
S str PyStringObject

      Extest2.c
[cpp] view plain copy
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<string.h>  
  4.   
  5. int fac(int n)  
  6. {  
  7.     if (n < 2) return (1); /* 0! == 1! == 1 */  
  8.     return (n) * fac(n-1); /* n! == n * (n-1)!*/  
  9. }  
  10.   
  11. char *reverse(char *s)  
  12. {  
  13.     register char t,  
  14.         *p = s,  
  15.         *q = (s + (strlen(s)-1));  
  16.   
  17.     while (s &&(p < q))  
  18.     {  
  19.         t = *p;  
  20.         *p++ = *q;  
  21.         *q-- = t;  
  22.     }  
  23.     return s;  
  24. }  
  25.   
  26. int test()  
  27. {  
  28.     char s[BUFSIZ];  
  29.     printf("4! == %d\n", fac(4));  
  30.     printf("8! == %d\n", fac(8));  
  31.     printf("12! == %d\n", fac(12));  
  32.     strcpy(s, "abcdef");  
  33.     printf("reversing 'abcdef', we get '%s'\n", reverse(s));  
  34.     strcpy(s, "madam");  
  35.     printf("reversing 'madam', we get '%s'\n", reverse(s));  
  36.     return 0;  
  37. }  
  38.   
  39. #include "Python.h" // Python头文件  
  40.   
  41. // 两个包装函数Extest_fac和Extest_doppel  
  42. static PyObject *  
  43. Extest_fac(PyObject *self, PyObject *args)  
  44. {  
  45.     int num; // 解析结果  
  46.     if (!PyArg_ParseTuple(args, "i", &num)) // PyArg_ParseTuple(args, "i", &num)就是将传入的数据解析,i表示希望得到整型变量,如果传入正确,将它保存到num变量中  
  47.         return NULL; // 如果传入错误,返回NULL  
  48.     return (PyObject*)Py_BuildValue("i", fac(num)); // 直接将结果转为Python的整型返回  
  49. }  
  50.   
  51. static PyObject *  
  52. Extest_doppel(PyObject *self, PyObject *args)  
  53. {  
  54.     char *orig_str; // 原始字符串  
  55.     char *dupe_str; // 反转后的字符串  
  56.     PyObject* retval;  
  57.   
  58.     if (!PyArg_ParseTuple(args, "s", &orig_str))  
  59.         return NULL;  
  60.     retval = (PyObject*)Py_BuildValue("ss", orig_str, dupe_str=reverse(strdup(orig_str))); // 'ss'就表示返回一个含有两个字符串的元组,strdup是复制字符串,要复制一份原始字符串,不理解。  
  61.     free(dupe_str); // 释放内存,  
  62.     return retval;  
  63. }  
  64.   
  65. // 测试函数,将Extest1中的main()改为test()  
  66. static PyObject *  
  67. Extest_test(PyObject *self, PyObject *args)  
  68. {  
  69.     test();  
  70.     return (PyObject*)Py_BuildValue(""); // 返回空字符串给Python  
  71. }  
  72.   
  73. // 把包装函数列在某个地方,以便Python解释器能够导入并调用  
  74. static PyMethodDef  
  75. ExtestMethods[] = // 该数组由多个二维数组组成,每个数组包含一个函数的信息,最后的NULL表示列表的结束  
  76. {  
  77.     {"fac", Extest_fac, METH_VARARGS }, // 第一项是函数在Python中的名字,第二项是相应的包装函数的名字,第三项是METH_VARARGS常量,该常量表示参数以元组形式传入  
  78.     {"doppel", Extest_doppel, METH_VARARGS },  
  79.     {"test", Extest_test, METH_VARARGS },  
  80.     {NULL, NULL },  
  81. };  
  82.   
  83. // 模块初始化函数void initModule(),这部分代码在模块导入时被解释器调用  
  84. void initExtest()  
  85. {  
  86.     Py_InitModule("Extest", ExtestMethods);  
  87. }  
        22.2.3 编译
       使用distutils模块,该模块被用于编译、安装和分发模块、扩展和包。
       主要步骤:
  1. 创建setup.py;
  2. 通过运行setup.py来编译和连接代码;
  3. 从Python中导入模块;
  4. 测试功能。       
       setup.py
[python] view plain copy
  1. #-*-coding: utf-8-*-  
  2.   
  3. from distutils.core import setup, Extension  
  4.   
  5. MOD = 'Extest'  
  6. setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest2.c'])])   
  7. # 在编译扩展之前,需为每一个扩展创建Extension实例,本例中是'Extest',后面跟的sources是所有源代码的文件列表,本例中是'Extest2.c'  
  8. # setup()需两个参数,一个是名字参数,表示要编译哪个东西,另外一个是列表,列出要编译的对象  
扩展python

         22.2.4 导入和测试
扩展python

扩展python

        22.2.5 引用计数
        两类引用:拥有引用和借引用。(这部分不知道要干嘛)

       22.2.6 线程和全局解释器锁(GIL) (同上)

       22.2.7 相关话题
       嵌入:把Python解释器包装到C的程序中。

相关文章: