點擊進入項目python
這一次咱們嘗試一下略微複雜的c程序。git
#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
divide()
函數是一個返回多個值的C函數例子,其中有一個是經過指針參數的方式。github
avg()
函數經過一個C數組執行數據彙集操做。數組
Point
和 distance()
函數涉及到了C結構體。ide
/* sample.c */ #include "sample.h" /* Compute the greatest common divisor */ int gcd(int x, int y) { int g = y; while (x > 0) { g = x; x = y % x; y = g; } return g; } /* 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; } /* Divide two numbers */ int divide(int a, int b, int *remainder) { int quot = a / b; *remainder = a % b; return quot; } /* 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; } /* Function involving a C data structure */ double distance(Point *p1, Point *p2) { return hypot(p1->x - p2->x, p1->y - p2->y); }
生成so文件後,咱們嘗試調用這些方法。函數
.argtypes
屬性是一個元組,包含了某個函數的輸入按時, 而 .restype
就是相應的返回類型。spa
ctypes
定義了大量的類型對象(好比c_double, c_int, c_short, c_float等), 表明了對應的C數據類型。指針
若是你想讓Python可以傳遞正確的參數類型而且正確的轉換數據的話, 那麼這些類型簽名的綁定是很重要的一步。若是你沒有這麼作,不但代碼不能正常運行, 還可能會致使整個解釋器進程掛掉。rest
import os import ctypes _mod = ctypes.cdll.LoadLibrary('./libsample.so')
實際上因爲這種函數的參數類型c語言和python語言中的類型是一一對應的,因此即便把.argtypes與
code.restype
註釋掉也能夠正常運行,但建議進行轉換
gcd:
原函數
/* Compute the greatest common divisor */ int gcd(int x, int y) { int g = y; while (x > 0) { g = x; x = y % x; y = g; } return g; }
調用
# int gcd(int, int) gcd = _mod.gcd gcd.argtypes = (ctypes.c_int, ctypes.c_int) gcd.restype = ctypes.c_int print(gcd(35, 42)) # 7
in_mandel:
原函數
/* 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; }
調用
# int in_mandel(double, double, int) in_mandel = _mod.in_mandel in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int) in_mandel.restype = ctypes.c_int print(in_mandel(0,0,500)) # 1
divide()
函數經過一個參數除以另外一個參數返回一個結果值,可是指針是python中不支持的操做。
/* Divide two numbers */ int divide(int a, int b, int *remainder) { int quot = a / b; *remainder = a % b; return quot; }
對於涉及到指針的參數,你一般須要先構建一個相應的ctypes對象並像下面這樣傳進去:
divide = _mod.divide x = ctypes.c_int() divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)) print(divide(10,3,x)) print(x.value)
在這裏,一個 ctypes.c_int
實例被建立並做爲一個指針被傳進去。 跟普通Python整形不一樣的是,一個 c_int
對象是能夠被修改的。 .value
屬性可被用來獲取或更改這個值。
更通常的,對於帶指針的函數,咱們會將其加一層封裝後調用,使得經過指針修改的變量經過return返回,這樣去c style,使得代碼更像python風格:
# int divide(int, int, int *) _divide = _mod.divide _divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)) _divide.restype = ctypes.c_int def divide(x, y): rem = ctypes.c_int() quot = _divide(x,y,rem) return quot, rem.value
avg()
函數又是一個新的挑戰。C代碼指望接受到一個指針和一個數組的長度值。 可是,在Python中,咱們必須考慮這個問題:數組是啥?它是一個列表?一個元組? 仍是 array
模塊中的一個數組?仍是一個 numpy
數組?仍是說全部都是? 實際上,一個Python「數組」有多種形式,你可能想要支持多種可能性。
/* 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; }
python -> c數組
(ctypes.c_int * 數組長度)(數組元素)
內在邏輯是:對於列表和元組,from_list
方法將其轉換爲一個 ctypes
的數組對象
nums = [1, 2, 3] a = (ctypes.c_int * len(nums))(3,4,5) print(a) print(a[0], a[1], a[2]) # <__main__.c_int_Array_3 object at 0x7f2767d4fd08> # 3 4 5
array對象自己存儲結構和c數組一致,直接提取內存地址傳給c指針便可:
import array a = array.array('d',[1,2,3]) print(a) ptr = a.buffer_info() # 返回tuple:(地址, 長度) print(ptr[0]) print(ctypes.cast(ptr[0], ctypes.POINTER(ctypes.c_double))) # 目標地址存入指針
numpy數組自帶ctypes.data_as(ctypes指針)方法,更爲方便。
有如上基礎,咱們能夠作出將python序列轉化爲c數組指針的class(這個class我沒有徹底弄懂其含義),並封裝avg函數:
# void avg(double *, int n) # Define a special type for the 'double *' argument class DoubleArrayType: def from_param(self, param): typename = type(param).__name__ if hasattr(self, 'from_' + typename): return getattr(self, 'from_' + typename)(param) elif isinstance(param, ctypes.Array): return param else: raise TypeError("Can't convert %s" % typename) # Cast from array.array objects def from_array(self, param): if param.typecode != 'd': raise TypeError('must be an array of doubles') ptr, _ = param.buffer_info() return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double)) # Cast from lists/tuples def from_list(self, param): val = ((ctypes.c_double)*len(param))(*param) return val from_tuple = from_list # Cast from a numpy array def from_ndarray(self, param): return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) DoubleArray = DoubleArrayType() _avg = _mod.avg _avg.argtypes = (DoubleArray, ctypes.c_int) _avg.restype = ctypes.c_double def avg(values): return _avg(values, len(values))
/* A C data structure */ typedef struct Point { double x,y; } Point; /* Function involving a C data structure */ double distance(Point *p1, Point *p2) { return hypot(p1->x - p2->x, p1->y - p2->y); }
繼承ctypes.Structure,_fields_命名結構體內元素便可:
# struct Point { } class Point(ctypes.Structure): _fields_ = [('x', ctypes.c_double), ('y', ctypes.c_double)] # double distance(Point *, Point *) distance = _mod.distance distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point)) distance.restype = ctypes.c_double
>>> p1 = Point(1,2) >>> p2 = Point(4,5) >>> p1.x 1.0 >>> p1.y 2.0 >>> distance(p1,p2) 4.242640687119285 >>>