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文件就會被修改添加不少語句,因此記得備份。