python深入和扩展_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

#include 

#include 

#include 

#define BUFSIZE 10

intfac(intn) {

if(n 

return1;

returnn * fac(n - 1);

}

//字符串反转

char*reverse(char*s) {

registerchart;

char*p = s;

char*q = (s + (strlen(s) - 1));

while(p 

t = *p;

*p++ = *q;

*q-- = t;

}

returns;

}

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

使用样板分为4步:

1.添加Python的头文件

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

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

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

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

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

Extest.c

#include 

#include 

#include 

#include "Python.h"      //包含python头文件

#define BUFSIZE 10

intfac(intn) {

if(n 

return1;

returnn * fac(n - 1);

}

char*reverse(char*s) {

registerchart;

char*p = s;

char*q = (s + (strlen(s) - 1));

while(p 

t = *p;

*p++ = *q;

*q-- = t;

}

returns;

}

//fac函数的包装函数

staticPyObject *

Extest_fac(PyObject *self, PyObject *args) {

intnum;

if(!(PyArg_ParseTuple(args, "i", &num))) {

returnNULL;

}

return(PyObject *)Py_BuildValue("i", fac(num));

}

//reverse函数的包装函数

staticPyObject *

Extest_doppel(PyObject *self, PyObject *args) {

char*orignal;

char*reversed;

PyObject * retval;

if(!(PyArg_ParseTuple(args, "s", &orignal))) {

returnNULL;

}

retval = (PyObject *)Py_BuildValue("ss", orignal, reversed=reverse(strdup(orignal)));

free(reversed);

returnretval;

}

//为模块创建一个函数信息的数组

staticPyMethodDef

ExtestMethods[] = {

{"fac", Extest_fac, METH_VARARGS},

{"doppel", Extest_doppel, METH_VARARGS},

};

//增加模块初始化函数

voidinitExtest() {

Py_InitModule("Extest", ExtestMethods);

}

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

setup.py

from distutils.core import setup, Extension

MOD = 'Extest'

setup(name=MOD, ext_modules=[

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标准库函数会产生优化效果

脚本一

importtimeit

importrandom

defgenerate(num):

whilenum:

yieldrandom.randrange(10)

num -= 1

print(timeit.timeit("sum(generate(999))", setup="from __main__ import generate", number=1000))

脚本二

importtimeit

fromctypes importcdll

defgenerate_c(num):

#Load standard C library

libc = cdll.LoadLibrary("libc.so.6") #Linux

# libc = cdll.msvcrt #Windows

whilenum:

yieldlibc.rand() % 10

num -= 1

print(timeit.timeit("sum(generate_c(999))", setup="from __main__ import generate_c", number=1000))

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

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

步骤一创建应用程序代码

/* functions.c */

#include "stdio.h"

#include "stdlib.h"

#include "string.h"

/* http://rosettacode.org/wiki/Sorting_algorithms/Merge_sort#C */

inline

voidmerge(int*left, intl_len, int*right, intr_len, int*out)

{

inti, j, k;

for(i = j = k = 0; i 

out[k++] = left[i] 

while(i 

while(j 

}

/* inner recursion of merge sort */

voidrecur(int*buf, int*tmp, intlen)

{

intl = len / 2;

if(len <= 1) return;

/* note that buf and tmp are swapped */

recur(tmp, buf, l);

recur(tmp + l, buf + l, len - l);

merge(tmp, l, tmp + l, len - l, buf);

}

/* preparation work before recursion */

voidmerge_sort(int*buf, intlen)

{

/* call alloc, copy and free only once */

int*tmp = malloc(sizeof(int) * len);

memcpy(tmp, buf, sizeof(int) * len);

recur(buf, tmp, len);

free(tmp);

}

intfibRec(intn){

if(n 

returnn;

else

returnfibRec(n-1) + fibRec(n-2);

}

~

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

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

gcc -shared -o libfunctions.so functions.o

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

functions.py

fromctypes import*

importtime

libfunctions = cdll.LoadLibrary("./libfunctions.so")

deffibRec(n):

ifn<2 :

returnn

else:

returnfibRec(n-1) + fibRec(n-2)

start = time.time()

fibRec(32)

finish = time.time()

print("Python: "+ str(finish - start))

#C Fibonacci

start = time.time()

x = libfunctions.fibRec(32)

finish = time.time()

print("C: "+ str(finish - start))

functions2.py

fromctypes import*

importtime

libfunctions = cdll.LoadLibrary("./libfunctions.so")

#Python Merge Sort

fromrandom importshuffle, sample

#Generate 9999 random numbers between 0 and 100000

numbers = sample(range(100000), 9999)

shuffle(numbers)

c_numbers = (c_int * len(numbers))(*numbers)

fromheapq importmerge

defmerge_sort(m):

iflen(m) <= 1:

returnm

middle = len(m) // 2

left = m[:middle]

right = m[middle:]

left = merge_sort(left)

right = merge_sort(right)

returnlist(merge(left, right))

start = time.time()

numbers = merge_sort(numbers)

finish = time.time()

print("Python: "+ str(finish - start))

#C Merge Sort

start = time.time()

libfunctions.merge_sort(byref(c_numbers), len(numbers))

finish = time.time()

print("C: "+ str(finish - start))

~

~

~

运行结果

可以看出纯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代码

#p1.py

importmath

defgreat_circle(lon1,lat1,lon2,lat2):

radius = 3956 #miles

x = math.pi/180.0

a = (90.0-lat1)*(x)

b = (90.0-lat2)*(x)

theta = (lon2-lon1)*(x)

c = math.acos((math.cos(a)*math.cos(b)) +

(math.sin(a)*math.sin(b)*math.cos(theta)))

returnradius*c

Python代码

#p1_test.py

importtimeit

lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826

num = 500000

t = timeit.Timer("p1.great_circle(%f,%f,%f,%f)"% (lon1,lat1,lon2,lat2),

"import p1")

print"Pure python function", t.timeit(num), "sec"

Python代码

# 测试结果

Pure python function 2.25580382347 sec

Cython: 使用Python的math模块

Python代码

#c1.pyx

importmath

defgreat_circle(float lon1,float lat1,float lon2,float lat2):

cdef float radius = 3956.0

cdef float pi = 3.14159265

cdef float x = pi/180.0

cdef float a,b,theta,c

a = (90.0-lat1)*(x)

b = (90.0-lat2)*(x)

theta = (lon2-lon1)*(x)

c = math.acos((math.cos(a)*math.cos(b)) + (math.sin(a)*math.sin(b)*math.cos(theta)))

returnradius*c

Python代码

# setup.py

fromdistutils.core importsetup

fromdistutils.extension importExtension

fromCython.Distutils importbuild_ext

ext_modules=[

Extension("c1",

["c1.pyx"])

]

setup(

name = "Demos",

cmdclass = {"build_ext": build_ext},

ext_modules = ext_modules

)

python setup.py build_ext --inplace

Python代码

#c1_test.py

importtimeit

lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826

num = 500000

t = timeit.Timer("c1.great_circle(%f,%f,%f,%f)"% (lon1,lat1,lon2,lat2),

"import c1")

print"Pure python function", t.timeit(num), "sec"

Python代码

#执行结果:

Pure python function 1.87078690529 sec

Cython:使用C的math库

Python代码

#c2.pyx

cdef extern from"math.h":

float cosf(float theta)

float sinf(float theta)

float acosf(float theta)

defgreat_circle(float lon1,float lat1,float lon2,float lat2):

cdef float radius = 3956.0

cdef float pi = 3.14159265

cdef float x = pi/180.0

cdef float a,b,theta,c

a = (90.0-lat1)*(x)

b = (90.0-lat2)*(x)

theta = (lon2-lon1)*(x)

c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))

returnradius*c

Python代码

#setup.py

fromdistutils.core importsetup

fromdistutils.extension importExtension

fromCython.Distutils importbuild_ext

ext_modules=[

Extension("c2",

["c2.pyx"],

libraries=["m"]) # Unix-like specific

]

setup(

name = "Demos",

cmdclass = {"build_ext": build_ext},

ext_modules = ext_modules

)

python setup.py build_ext --inplace

Python代码

# c2_test.py

importtimeit

lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826

num = 500000

t = timeit.Timer("c2.great_circle(%f,%f,%f,%f)"% (lon1,lat1,lon2,lat2),

"import c2")

print"Pure python function", t.timeit(num),"sec"

Python代码

#执行结果

Pure python function 0.34069108963 sec

Cython:使用C函数

Python代码

#c3.pyx

cdef extern from"math.h":

float cosf(float theta)

float sinf(float theta)

float acosf(float theta)

cdef float _great_circle(float lon1,float lat1,float lon2,float lat2):

cdef float radius = 3956.0

cdef float pi = 3.14159265

cdef float x = pi/180.0

cdef float a,b,theta,c

a = (90.0-lat1)*(x)

b = (90.0-lat2)*(x)

theta = (lon2-lon1)*(x)

c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))

returnradius*c

defgreat_circle(float lon1,float lat1,float lon2,float lat2,int num):

cdef int i

cdef float x

fori from0 <= i 

x = _great_circle(lon1,lat1,lon2,lat2)

returnx

C-sharp代码

#setup.py

from distutils.core import setup

from distutils.extension import Extension

from Cython.Distutils import build_ext

ext_modules=[

Extension("c3",

["c3.pyx"],

libraries=["m"]) # Unix-like specific

]

setup(

name = "Demos",

cmdclass = {"build_ext": build_ext},

ext_modules = ext_modules

)

python setup.py build_ext --inplace

Python代码

#c3_test.py

importtimeit

lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826

num = 500000

t = timeit.Timer("c2.great_circle(%f,%f,%f,%f)"% (lon1,lat1,lon2,lat2),

"import c2")

print"Pure python function", t.timeit(num), "sec"

Python代码

#测试结果

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#

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值