『Python CoolBook』Cython

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

csample.pxd:Cython頭文件

.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

sample.pyx:Cython封裝主體

程序以下,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語句支持的很好。

in_mandel:返回值爲0或1(布爾整形)

/* 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)

divide:形參指針對象

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同樣的指向它的指針。

avg:形參數組&GIL釋放

/* 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() 的這個例子還展現瞭如何處理全局解釋器鎖。

  1. 語句 with nogil: 聲明瞭一個不須要GIL就能執行的代碼塊。 在這個塊中,不能有任何的普通Python對象——只能使用被聲明爲 cdef 的對象和函數(pxd中的)。
  2. 另外,外部函數必須現實的聲明它們能不依賴GIL就能執行。 所以,在csample.pxd文件中,avg() 被聲明爲 double avg(double *, int) nogil .

distance、Point:結構體處理

 本節使用膠囊對象將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 >>>

 setup.py

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

相關文章
相關標籤/搜索