zoukankan      html  css  js  c++  java
  • Python 扩展技术总结(转)

    •  

        一般来说,所有能被整合或导入到其他Python脚本中的代码,都可以称为扩展。你可以用纯Python来写扩展,也可以用C/C++之类的编译型语言来写扩展,甚至可以用java,C都可以来写 python扩展。Python的一大特点是,扩展和解释器之间的交互方式域普通的Python模块完全一样,Python的模块导入机制非常抽象,抽象到让使用模块的代码无法了解到模块的具体实现细节。

        Python进行扩展的主要原因有三点:(1)添加额外的Python语言的核心部分没有提供的功能 2)为了提升性能瓶颈的效率(3)保持专有源代码私密,把一部分代码从Python转到编译语言可以保持专有源代码私密

    方法一:利用C API进行C扩展

    这种方法是最基本的,包括三个步骤:1.创建应用程序代码 2.利用样板来包装代码 3.编译1.创建一个Extest.c文件 包含两个C函数

    Extest.c

     

    [cpp] view plain copy

     

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <string.h>  
    4.   
    5. #define BUFSIZE 10  
    6.   
    7. int fac(int n) {  
    8.     if (n < 2)  
    9.         return 1;  
    10.     return n * fac(n - 1);  
    11. }  
    12.   
    13.  //字符串反转  
    14. char *reverse(char *s) {  
    15.     register char t;  
    16.     char *p = s;  
    17.     char *q = (s + (strlen(s) - 1));  
    18.     while (p < q) {  
    19.         t = *p;  
    20.         *p++ = *q;  
    21.         *q-- = t;  
    22.     }  
    23.     return s;  
    24. }  
    25.  

    2.用样板来包装你的代码

     

    使用样板分为4步:

    1.添加Python的头文件

    2.为每一个模块的每个函数增加一个形如PyObject* Module_func()的包装函数

    3.为每一个模块增加一个形如PyMethodDef ModuleMethods[]的数组

    4.增加模块初始化函数 void initModule()

    第二步的处理需要一些技巧,你需要为所有想Python环境访问的函数增加一个静态函数。函数的返回值类型为 PyObject*,PythonC语言扩展接口中,大部分函数都有一个或者多个参数为PyObject指针类型,并且返回值也大都为PyObject指针. 包装函数的用处是先把Python的值传递给C,然后调用C函数把函数的计算结果转换成Python 对象,然后返回给Python

    第三步每一个数组都包含一个 函数信息,包括函数Python的名字,相应的包装函数的名字以及一个METH_VARARGS常量(表示参数以元组的形式传入)

    Extest.c

     

    [cpp] view plain copy

     

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <string.h>  
    4. #include "Python.h"      //包含python头文件  
    5. #define BUFSIZE 10  
    6.   
    7. int fac(int n) {  
    8.     if (n < 2)  
    9.         return 1;  
    10.     return n * fac(n - 1);  
    11. }  
    12.   
    13. char *reverse(char *s) {  
    14.     register char t;  
    15.     char *p = s;  
    16.     char *q = (s + (strlen(s) - 1));  
    17.     while (p < q) {  
    18.         t = *p;  
    19.         *p++ = *q;  
    20.         *q-- = t;  
    21.     }  
    22.     return s;  
    23. }  
    24.   
    25. //fac函数的包装函数    
    26. static PyObject *  
    27. Extest_fac(PyObject *self, PyObject *args) {  
    28.     int num;  
    29.     if (!(PyArg_ParseTuple(args, "i", &num))) {  
    30.         return NULL;  
    31.     }  
    32.     return (PyObject *)Py_BuildValue("i", fac(num));  
    33. }  
    34. //reverse函数的包装函数  
    35. static PyObject *  
    36. Extest_doppel(PyObject *self, PyObject *args) {  
    37.     char *orignal;  
    38.     char *reversed;  
    39.     PyObject * retval;  
    40.     if (!(PyArg_ParseTuple(args, "s", &orignal))) {  
    41.         return NULL;  
    42.     }  
    43.     retval = (PyObject *)Py_BuildValue("ss", orignal, reversed=reverse(strdup(orignal)));  
    44.     free(reversed);  
    45.     return retval;  
    46. }  
    47. //为模块创建一个函数信息的数组  
    48. static PyMethodDef  
    49. ExtestMethods[] = {  
    50.     {"fac", Extest_fac, METH_VARARGS},  
    51.     {"doppel", Extest_doppel, METH_VARARGS},  
    52. };  
    53. //增加模块初始化函数  
    54. void initExtest() {  
    55.     Py_InitModule("Extest", ExtestMethods);  
    56. }  

     

    在编译阶段需要创建一个setup.py,通过setup.py来编译和链接代码。这一步完成后就就可以直接导入这个扩展的模块了.setup.py中需要导入distutils包。首先你要为每一个扩展创建一个Extension实例,然后编译的操作主要由setup()函数来完成,它需要两个参数:一个名字参数表示要编译哪个东西,一个列表表示要编译的对象

    setup.py

     

    [cpp] view plain copy

     

    1. <pre name="code" class="python">from distutils.core import setup, Extension  
    2.   
    3. MOD = 'Extest'  
    4. setup(name=MOD, ext_modules=[  
    5.         Extension(MOD, sources=['Extest.c'])])  

    运行setup.py   :执行命令 python setup.py build

    执行的结果 是在当前目录中生成一个build 目录,在此目录下有一个Extest.so文件,然后就可以在脚本中import这个模块了

     

     

    方法二:利用Ctypes进行C扩展

     

    为了扩展Python,我们可以C/C++编写模块,但是这要求对Python的底层有足够的了解,包括Python对象模

    型、常用模块、引用计数等,门槛较高,且不方便利用现有的C库。ctypes 则另辟蹊径,通过封装

    dlopen/dlsym之类的函数,并提供对C中数据结构的包装/解包,让Python能够加载动态库、导出其中的函数直

    接加以利用。

     

    一个简单的实例:

     

     

    这个例子直接利用ctypes使用C标准库函数而不用编写C代码,使用C标准库函数会产生优化效果

    脚本一

    [python] view plain copy

     

    1. import timeit  
    2. import random  
    3.   
    4. def generate(num):  
    5.     while num:  
    6.         yield random.randrange(10)  
    7.         num -= 1  
    8.   
    9. print(timeit.timeit("sum(generate(999))", setup="from __main__ import generate", number=1000))  

     

    [python] view plain copy

     

    1. <span style="font-size:18px;color:#FF0000;">脚本二</span>  
    2. import timeit  
    3. from ctypes import cdll  
    4.   
    5. def generate_c(num):  
    6. #Load standard C library    
    7.        libc = cdll.LoadLibrary("libc.so.6"#Linux  
    8.        # libc = cdll.msvcrt #Windows    
    9.        while num:  
    10.             yield libc.rand() % 10  
    11.             num -= 1  
    12.   
    13. print(timeit.timeit("sum(generate_c(999))", setup="from __main__ import generate_c", number=1000))  

     

    第一个脚本使用 Python的随机函数,运行时间约为1.067 第二个脚本使用C的随机函数 运行时间约为0.423

     

    我们也利用Ctypes可以自己写模块导入使用:

    步骤一创建应用程序代码

     

    [cpp] view plain copy

     

    1. /* functions.c */  
    2. #include "stdio.h"  
    3. #include "stdlib.h"  
    4. #include "string.h"  
    5.   
    6. /* http://rosettacode.org/wiki/Sorting_algorithms/Merge_sort#C */  
    7. inline  
    8. void merge(int *left, int l_len, int *right, int r_len, int *out)  
    9. {  
    10.     int i, j, k;  
    11.     for (i = j = k = 0; i < l_len && j < r_len; )  
    12.         out[k++] = left[i] < right[j] ? left[i++] : right[j++];  
    13.   
    14.     while (i < l_len) out[k++] = left[i++];  
    15.     while (j < r_len) out[k++] = right[j++];  
    16. }  
    17.   
    18. /* inner recursion of merge sort */  
    19. void recur(int *buf, int *tmp, int len)  
    20. {  
    21.     int l = len / 2;  
    22.     if (len <= 1) return;  
    23.   
    24.     /* note that buf and tmp are swapped */  
    25.     recur(tmp, buf, l);  
    26.     recur(tmp + l, buf + l, len - l);  
    27.   
    28.     merge(tmp, l, tmp + l, len - l, buf);  
    29. }  
    30.   
    31. /* preparation work before recursion */  
    32. void merge_sort(int *buf, int len)  
    33. {  
    34.     /* call alloc, copy and free only once */  
    35.     int *tmp = malloc(sizeof(int) * len);  
    36.     memcpy(tmp, buf, sizeof(int) * len);  
    37.   
    38.     recur(buf, tmp, len);  
    39.   
    40.     free(tmp);  
    41. }  
    42.   
    43. int fibRec(int n){  
    44.     if(n < 2)  
    45.         return n;  
    46.     else  
    47.         return fibRec(n-1) + fibRec(n-2);  
    48. }  
    49. ~      

    步骤二:将它编译链接为.so文件

    执行指令:gcc -Wall -fPIC -c functions.c

                        gcc -shared -o libfunctions.so functions.o

    步骤三:在自己写的python脚本中使用这些库,下面的脚本分别用纯python的模块(输出时前面加了python),和我们导入的扩展(输出时前面加了C)模块来运行,对比其模块执行时间

    functions.py

     

    [python] view plain copy

     

    1. from ctypes import *  
    2. import time  
    3.   
    4. libfunctions = cdll.LoadLibrary("./libfunctions.so")  
    5.   
    6. def fibRec(n):  
    7.         if n<2 :  
    8.                 return n  
    9.         else:  
    10.                 return fibRec(n-1) + fibRec(n-2)  
    11.   
    12. start = time.time()  
    13. fibRec(32)  
    14. finish = time.time()  
    15. print("Python: " + str(finish - start))  
    16.   
    17. #C Fibonacci    
    18. start = time.time()  
    19. x = libfunctions.fibRec(32)  
    20. finish = time.time()  
    21. print("C: " + str(finish - start))  

     

    functions2.py

    [python] view plain copy

     

    1. from ctypes import *  
    2. import time  
    3.   
    4. libfunctions = cdll.LoadLibrary("./libfunctions.so")  
    5.   
    6. #Python Merge Sort    
    7. from random import shuffle, sample  
    8.   
    9. #Generate 9999 random numbers between 0 and 100000    
    10. numbers = sample(range(100000), 9999)  
    11. shuffle(numbers)  
    12. c_numbers = (c_int * len(numbers))(*numbers)  
    13.   
    14. from heapq import merge  
    15.   
    16. def merge_sort(m):  
    17.     if len(m) <= 1:  
    18.         return m  
    19.   
    20.     middle = len(m) // 2  
    21.     left = m[:middle]  
    22.     right = m[middle:]  
    23.   
    24.     left = merge_sort(left)  
    25.     right = merge_sort(right)  
    26.     return list(merge(left, right))  
    27.   
    28. start = time.time()  
    29. numbers = merge_sort(numbers)  
    30. finish = time.time()  
    31. print("Python: " + str(finish - start))  
    32.   
    33. #C Merge Sort    
    34. start = time.time()  
    35. libfunctions.merge_sort(byref(c_numbers), len(numbers))  
    36. finish = time.time()  
    37. print("C: " + str(finish - start))  
    38. ~                                                                                 
    39. ~                                                                                 
    40. ~                        

     

    运行结果

     

     

    可以看出纯python模块的运行时间是我们写的扩展模块运行时间的十倍以上

     

    方法三:利用Cython 进行C扩展 (参考Cython三分钟入门)

     

               Cyhton 是一个用来快速生成 Python 扩展模块(extention module)的工具,它的语法是 python语言语法和 C 语言语法的混血。准确说 Cython 是单独的一门语言,专门用来写在 Python 里面import 用的扩展库。实际上 Cython 的语法基本上跟 Python 一致,而Cython 有专门的“编译器”;先将 Cython 代码转变成 C(自动加入了一大堆的 C-Python API),然后使用 C 编译器编译出最终的 Python可调用的模块。

           要注意的一点是, Cython 是用来生成 C 扩展到而不是独立的程序的。所有的加速都是针对一个已经存在的 Python 应用的一个函数进行的。没有使用 C 或 Lisp 重写整个应用程序,也没有手写 C 扩展 。只是用一个简单的方法来整合 C 的速度和 C 数据类型到 Python函数中去

          Cython 代码跟 Python 不一样,必须要编译。 编译经过两个阶段:(1) Cython 编译.pyx 文件为.c 文件 (2) C 编译器会把.c 文件编译成.so 文件.生成.so 文件后表示重写函数成功,可以在 python 代码中直接调用这个模块

     

    Python代码

    1. #p1.py  
    2. import math  
    3. def great_circle(lon1,lat1,lon2,lat2):  
    4.     radius = 3956 #miles  
    5.     x = math.pi/180.0  
    6.     a = (90.0-lat1)*(x)  
    7.     b = (90.0-lat2)*(x)  
    8.     theta = (lon2-lon1)*(x)  
    9.     c = math.acos((math.cos(a)*math.cos(b)) +  
    10.                   (math.sin(a)*math.sin(b)*math.cos(theta)))  
    11.     return radius*c  

     

    Python代码

    1. #p1_test.py  
    2. import timeit  
    3. lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826  
    4. num = 500000  
    5. t = timeit.Timer("p1.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),  
    6.                        "import p1")  
    7. print "Pure python function", t.timeit(num), "sec"  

    Python代码

    1. 测试结果  
    2. Pure python function 2.25580382347 sec  

    Cython: 使用Python的math模块

     

    Python代码

    1. #c1.pyx  
    2. import math  
    3. def great_circle(float lon1,float lat1,float lon2,float lat2):  
    4.     cdef float radius = 3956.0  
    5.     cdef float pi = 3.14159265  
    6.     cdef float x = pi/180.0  
    7.     cdef float a,b,theta,c  
    8.     a = (90.0-lat1)*(x)  
    9.     b = (90.0-lat2)*(x)  
    10.     theta = (lon2-lon1)*(x)  
    11.     c = math.acos((math.cos(a)*math.cos(b)) + (math.sin(a)*math.sin(b)*math.cos(theta)))  
    12.     return radius*c  

    Python代码

    1. # setup.py  
    2. from distutils.core import setup  
    3. from distutils.extension import Extension  
    4. from Cython.Distutils import build_ext  
    5. ext_modules=[  
    6.     Extension("c1",  
    7.               ["c1.pyx"])  
    8. ]  
    9. setup(  
    10.   name = "Demos",  
    11.   cmdclass = {"build_ext": build_ext},  
    12.   ext_modules = ext_modules  
    13. )  

    python setup.py build_ext --inplace

     

    Python代码

    1. #c1_test.py  
    2. import timeit  
    3. lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826  
    4. num = 500000  
    5. t = timeit.Timer("c1.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),  
    6.                        "import c1")  
    7. print "Pure python function", t.timeit(num), "sec"  

    Python代码

    1. #执行结果:  
    2. Pure python function 1.87078690529 sec   

    Cython:使用C的math库

     

    Python代码

    1. #c2.pyx  
    2. cdef extern from "math.h":  
    3.     float cosf(float theta)  
    4.     float sinf(float theta)  
    5.     float acosf(float theta)  
    6. def great_circle(float lon1,float lat1,float lon2,float lat2):  
    7.     cdef float radius = 3956.0  
    8.     cdef float pi = 3.14159265  
    9.     cdef float x = pi/180.0  
    10.     cdef float a,b,theta,c  
    11.     a = (90.0-lat1)*(x)  
    12.     b = (90.0-lat2)*(x)  
    13.     theta = (lon2-lon1)*(x)  
    14.     c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))  
    15.     return radius*c  

    Python代码

    1. #setup.py  
    2. from distutils.core import setup  
    3. from distutils.extension import Extension  
    4. from Cython.Distutils import build_ext  
    5. ext_modules=[  
    6.     Extension("c2",  
    7.               ["c2.pyx"],  
    8.               libraries=["m"]) # Unix-like specific  
    9. ]  
    10. setup(  
    11.   name = "Demos",  
    12.   cmdclass = {"build_ext": build_ext},  
    13.   ext_modules = ext_modules  
    14. )  

    python setup.py build_ext --inplace

    Python代码

    1. # c2_test.py  
    2. import timeit  
    3. lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826  
    4. num = 500000  
    5. t = timeit.Timer("c2.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),  
    6.                        "import c2")  
    7. print "Pure python function", t.timeit(num), "sec"  

    Python代码

    1. #执行结果  
    2. Pure python function 0.34069108963 sec   

     

    Cython:使用C函数

     

    Python代码

    1. #c3.pyx  
    2. cdef extern from "math.h":  
    3.     float cosf(float theta)  
    4.     float sinf(float theta)  
    5.     float acosf(float theta)  
    6. cdef float _great_circle(float lon1,float lat1,float lon2,float lat2):  
    7.     cdef float radius = 3956.0  
    8.     cdef float pi = 3.14159265  
    9.     cdef float x = pi/180.0  
    10.     cdef float a,b,theta,c  
    11.     a = (90.0-lat1)*(x)  
    12.     b = (90.0-lat2)*(x)  
    13.     theta = (lon2-lon1)*(x)  
    14.     c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))  
    15.     return radius*c  
    16. def great_circle(float lon1,float lat1,float lon2,float lat2,int num):  
    17.     cdef int i  
    18.     cdef float x  
    19.     for i from 0 <= i < num:  
    20.         x = _great_circle(lon1,lat1,lon2,lat2)  
    21.     return x  

    C-sharp代码

    1. #setup.py  
    2. from distutils.core import setup  
    3. from distutils.extension import Extension  
    4. from Cython.Distutils import build_ext  
    5. ext_modules=[  
    6.     Extension("c3",  
    7.               ["c3.pyx"],  
    8.               libraries=["m"]) # Unix-like specific  
    9. ]  
    10. setup(  
    11.   name = "Demos",  
    12.   cmdclass = {"build_ext": build_ext},  
    13.   ext_modules = ext_modules  
    14. )  

    python setup.py build_ext --inplace

    Python代码

    1. #c3_test.py  
    2. import timeit  
    3. lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826  
    4. num = 500000  
    5. t = timeit.Timer("c2.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),  
    6.                        "import c2")  
    7. print "Pure python function", t.timeit(num), "sec"  

    Python代码

    1. #测试结果  
    2. Pure python function 0.340164899826 sec   

    测试结论

     

    Python代码

      • # python代码  
      • Pure python function 2.25580382347 sec  
      • # Cython,使用Python的math模块  
      • Pure python function 1.87078690529 sec   
      • # Cython,使用C的math库  
      • Pure python function 0.34069108963 sec   
      • # Cython,使用纯粹的C函数  
      • Pure python function 0.340164899826 sec  

    注意事项:

                通过cython扩展python 模块时出现“ImportError: No module named Cython.Build“的解决方法如下

                pip install Cython

                pip install fasttext

                这个pip必须与当前python版本相一致

    参考:  https://blog.csdn.net/u010786109/article/details/41825147#

  • 相关阅读:
    xapian的使用
    Andriod 环境配置以及第一个Android Application Project
    2013Esri全球用户大会之ArcGIS for Server&Portal for ArcGIS
    window server 2012 更改密钥 更改系统序列号
    持续集成之路——数据访问层的单元测试(续)
    多项式相乘与相加演示
    hdu 1847 博弈基础题 SG函数 或者规律2种方法
    solaris之cpu
    Android音效SoundPool问题:soundpool 1 not retry
    poj1845-Sumdiv
  • 原文地址:https://www.cnblogs.com/it-tsz/p/8726797.html
Copyright © 2011-2022 走看看