這一次我們嘗試一下略微復雜的c程序。
一、C程序
頭文件:
#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函數例子,其中有一個是通過指針參數的方式。
avg() 函數通過一個C數組執行數據聚集操作。
Point 和 distance() 函數涉及到了C結構體。
/* 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文件后,我們嘗試調用這些方法。
二、Python封裝
.argtypes 屬性是一個元組,包含了某個函數的輸入按時, 而 .restype 就是相應的返回類型。
ctypes 定義了大量的類型對象(比如c_double, c_int, c_short, c_float等), 代表了對應的C數據類型。
如果你想讓Python能夠傳遞正確的參數類型並且正確的轉換數據的話, 那么這些類型簽名的綁定是很重要的一步。如果你沒有這么做,不但代碼不能正常運行, 還可能會導致整個解釋器進程掛掉。
導入c庫文件
import os
import ctypes
_mod = ctypes.cdll.LoadLibrary('./libsample.so')
簡單數據類型函數封裝
實際上由於這種函數的參數類型c語言和python語言中的類型是一一對應的,所以即使把.argtypes與.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 >>>
