github地址html
使用Cython导入库的话,须要一下几个文件:python
.c:C函数源码git
.h:C函数头github
.pxd:Cython函数头数组
.pyx:包装函数app
setup.py:pythonide
本节示例.c和.h文件同『Python CoolBook』使用ctypes访问C代码_下_demo进阶即存在sample.c和sample.h两个源文件。函数
cdef:Cython函数,只能在Cython中调用,python识别不了这个定义后面的主体,并且它后面也不只仅接函数,class等都可,def定义的函数能够被Python识别,在pxd和pyx中都可使用。post
.pxd文件仅仅只包含C定义(相似.h文件),即至关于将.h文件包装为Cython的头。注意,.pxd仅仅是声明定义,咱们此时并未对函数作包装,这个工做在.pyx中完成。ui
# csample.pxd # # Declarations of "external" C functions and structures cdef extern from "sample.h": int gcd(int, int) bint in_mandel(double, double, int) int divide(int, int, int *) double avg(double *, int) nogil ctypedef struct Point: double x double y double distance(Point *, Point *)
为例对比,咱们给出sample.h文件以下:
#ifndef __SAMPLE_H__ #define __SAMPLE_H__ #include <math.h> #ifdef __cplusplus extern "C" { #endif int gcd(int x, int y); int in_mandel(double x0, double y0, int n); int divide(int a, int b, int *remainder); double avg(double *a, int n); /* A C data structure */ typedef struct Point { double x,y; } Point; double distance(Point *p1, Point *p2); #ifdef __cplusplus } #endif #endif
程序以下,pyx文本中语法和python一致,可是却能够像C中同样指定形参类型(也能够不指定),实际上这里是最基础的包装,能够看到就是调用了csample包中的函数,咱们在后文中能够看到对基本包装的增强。
# sample.pyx # Import the low-level C declarations cimport csample # Import some functionality from Python and the C stdlib from cpython.pycapsule cimport * from libc.stdlib cimport malloc, free # Wrappers def gcd(unsigned int x, unsigned int y): return csample.gcd(x, y) def in_mandel(x, y, unsigned int n): return csample.in_mandel(x, y, n) def divide(x, y): cdef int rem quot = csample.divide(x, y, &rem) return quot, rem def avg(double[:] a): cdef: int sz double result sz = a.size with nogil: result = csample.avg(<double *> &a[0], sz) return result # Destructor for cleaning up Point objects cdef del_Point(object obj): pt = <csample.Point *> PyCapsule_GetPointer(obj,"Point") free(<void *> pt) # Create a Point object and return as a capsule def Point(double x,double y): cdef csample.Point *p p = <csample.Point *> malloc(sizeof(csample.Point)) if p == NULL: raise MemoryError("No memory to make a Point") p.x = x p.y = y return PyCapsule_New(<void *>p,"Point",<PyCapsule_Destructor>del_Point) def distance(p1, p2): pt1 = <csample.Point *> PyCapsule_GetPointer(p1,"Point") pt2 = <csample.Point *> PyCapsule_GetPointer(p2,"Point") return csample.distance(pt1,pt2)
因为不少细节都蕴含在上面代码中,也涉及不少前面介绍过的高级特性,包括数组操做、包装隐形指针和释放GIL,因此下面逐个分析各个函数。
gcd:简单的数字参数函数
csample.pxd
文件声明了 int gcd(int, int)
函数, sample.pyx
中的包装函数以下:
cimport csample def gcd(unsigned int x, unsigned int y): # <--- 无符号整形 return csample.gcd(x,y)
无符号整型使得在运行中接收到负数会报这一行的错误,咱们能够修改以下,
# def gcd(unsigned int x, unsigned int y): # return csample.gcd(x, y) def gcd(int x, int y): if x <= 0: raise ValueError("x must be > 0") if y <= 0: raise ValueError("y must be > 0") return csample.gcd(x,y)
能够看到,这里对Python语句支持的很好。
/* Test if (x0,y0) is in the Mandelbrot set or not */ int in_mandel(double x0, double y0, int n) { double x=0,y=0,xtemp; while (n > 0) { xtemp = x*x - y*y + x0; y = 2*x*y + y0; x = xtemp; n -= 1; if (x*x + y*y > 4) return 0; } return 1; }
pxd声明能够指定函数返回类型bint:
bint in_mandel(double, double, int)
int divide(int a, int b, int *remainder) { int quot = a / b; *remainder = a % b; return quot; }
python无法传递一个地址,但pyx能够,
def divide(x, y): cdef int rem quot = csample.divide(x, y, &rem) return quot, rem
在这里,rem
变量被显示的声明为一个C整型变量。 当它被传入 divide()
函数的时候,&rem
建立一个跟C同样的指向它的指针。
/* Average values in an array */ double avg(double *a, int n) { int i; double total = 0.0; for (i = 0; i < n; i++) { total += a[i]; } return total / n; }
avg()
函数的代码演示了Cython更高级的特性:
def avg(double[:] a): cdef: int sz double result sz = a.size with nogil: result = csample.avg(<double *> &a[0], sz) return result
首先 def avg(double[:] a)
声明了 avg()
接受一个一维的双精度内存视图。 最惊奇的部分是返回的结果函数能够接受任何兼容的数组对象,包括被numpy建立的。例如:
>>> import array >>> a = array.array('d',[1,2,3]) >>> import numpy >>> b = numpy.array([1., 2., 3.]) >>> import sample >>> sample.avg(a) 2.0 >>> sample.avg(b) 2.0 >>>
在此包装器中,a.size
和 &a[0]
分别引用数组元素个数和底层指针。 语法 <double *> &a[0]
教你怎样将指针转换为不一样的类型。 前提是C中的 avg()
接受一个正确类型的指针。 参考下一节关于Cython内存视图的更高级讲述。
除了处理一般的数组外,avg()
的这个例子还展现了如何处理全局解释器锁。
with nogil:
声明了一个不须要GIL就能执行的代码块。 在这个块中,不能有任何的普通Python对象——只能使用被声明为 cdef
的对象和函数(pxd中的)。avg()
被声明为 double avg(double *, int) nogil
.本节使用胶囊对象将Point对象当作隐形指针来处理,pxd中声明以下,
ctypedef struct Point: double x double y
首先,下面的导入被用来引入C函数库和Python C API中定义的函数:
from cpython.pycapsule cimport * # <---胶囊结构函数库,直接来自Python C API from libc.stdlib cimport malloc, free
包装以下,先创建结构体,最后以胶囊形式返回:
# Destructor for cleaning up Point objects cdef del_Point(object obj): pt = <csample.Point *> PyCapsule_GetPointer(obj,"Point") # <---胶囊结构提取指针(胶囊结构还原结构体) free(<void *> pt) # Create a Point object and return as a capsule def Point(double x,double y): cdef csample.Point *p p = <csample.Point *> malloc(sizeof(csample.Point)) if p == NULL: raise MemoryError("No memory to make a Point") p.x = x p.y = y return PyCapsule_New(<void *>p,"Point",<PyCapsule_Destructor>del_Point)
函数 del_Point()
和 Point()
使用这个功能来建立一个胶囊对象, 它会包装一个 Point *
指针。
cdef del_Point()
将 del_Point()
声明为一个函数, 只能经过Cython访问,而不能从Python中访问。 所以,这个函数对外部是不可见的——它被用来当作一个回调函数来清理胶囊分配的内存。 函数调用好比 PyCapsule_New()
、PyCapsule_GetPointer()
直接来自Python C API以一样的方式被使用。
distance
函数从 Point()
建立的胶囊对象中提取指针,
def distance(p1, p2): pt1 = <csample.Point *> PyCapsule_GetPointer(p1,"Point") pt2 = <csample.Point *> PyCapsule_GetPointer(p2,"Point") return csample.distance(pt1,pt2)
这里要注意的是你不须要担忧异常处理。 若是一个错误的对象被传进来,PyCapsule_GetPointer()
会抛出一个异常, 可是Cython已经知道怎么查找到它,并将它从 distance()
传递出去。
处理Point结构体一个缺点是它的实现是不可见的。 你不能访问任何属性来查看它的内部。 这里有另一种方法去包装它,就是定义一个扩展类型,以下所示:
# sample.pyx cimport csample from libc.stdlib cimport malloc, free ... cdef class Point: cdef csample.Point *_c_point # 声明Point结构体 def __cinit__(self, double x, double y): # 初始化过程就是创建一个结构体 self._c_point = <csample.Point *> malloc(sizeof(csample.Point)) self._c_point.x = x self._c_point.y = y def __dealloc__(self): free(self._c_point) property x: # 方法修饰为属性 def __get__(self): return self._c_point.x def __set__(self, value): self._c_point.x = value property y: # 方法修饰为属性 def __get__(self): return self._c_point.y def __set__(self, value): self._c_point.y = value def distance(Point p1, Point p2): return csample.distance(p1._c_point, p2._c_point)
在这里,cdif类 Point
将Point声明为一个扩展类型。 类属性 cdef csample.Point *_c_point
声明了一个实例变量, 拥有一个指向底层Point结构体的指针。 __cinit__()
和 __dealloc__()
方法经过 malloc()
和 free()
建立并销毁底层C结构体。 x和y属性的声明让你获取和设置底层结构体的属性值。 distance()
的包装器还能够被修改,使得它能接受 Point
扩展类型实例做为参数, 而传递底层指针给C函数。
作了这个改变后,你会发现操做Point对象就显得更加天然了:
>>> import sample >>> p1 = sample.Point(2,3) >>> p2 = sample.Point(4,5) >>> p1 <sample.Point object at 0x100447288> >>> p2 <sample.Point object at 0x1004472a0> >>> p1.x 2.0 >>> p1.y 3.0 >>> sample.distance(p1,p2) 2.8284271247461903 >>>
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [ Extension('sample', ['sample.pyx'], libraries=['sample'], library_dirs=['.'])] setup( name = 'Sample extension module', cmdclass = {'build_ext': build_ext}, ext_modules = ext_modules )
python setup.py build_ext --inplace
注意,编译完成后sample.c文件就会被修改添加不少语句,因此记得备份。