Python Cookbook(第3版)中文版:15.9 用WSIG包裝C代碼
15.9 用WSIG包裝C代碼?
問題?
你想讓你寫的C代碼作為一個C擴展模塊來訪問,想通過使用 Swig包裝生成器 來完成。
解決方案?
Swig通過解析C頭文件並自動創建擴展代碼來操作。
要使用它,你先要有一個C頭文件。例如,我們示例的頭文件如下:
/* sample.h */
#include <math.h>
extern int gcd(int, int);
extern int in_mandel(double x0, double y0, int n);
extern int divide(int a, int b, int *remainder);
extern double avg(double *a, int n);
typedef struct Point {
double x,y;
} Point;
extern double distance(Point *p1, Point *p2);
一旦你有了這個頭文件,下一步就是編寫一個Swig”接口”文件。
按照約定,這些文件以”.i”後綴並且類似下面這樣:
// sample.i - Swig interface %module sample %{ #include "sample.h" %} /* Customizations */ %extend Point { /* Constructor for Point objects */ Point(double x, double y) { Point *p = (Point *) malloc(sizeof(Point)); p->x = x; p->y = y; return p; }; }; /* Map int *remainder as an output argument */ %include typemaps.i %apply int *OUTPUT { int * remainder }; /* Map the argument pattern (double *a, int n) to arrays */ %typemap(in) (double *a, int n)(Py_buffer view) { view.obj = NULL; if (PyObject_GetBuffer($input, &view, PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT) == -1) { SWIG_fail; } if (strcmp(view.format,"d") != 0) { PyErr_SetString(PyExc_TypeError, "Expected an array of doubles"); SWIG_fail; } $1 = (double *) view.buf; $2 = view.len / sizeof(double); } %typemap(freearg) (double *a, int n) { if (view$argnum.obj) { PyBuffer_Release(&view$argnum); } } /* C declarations to be included in the extension module */ extern int gcd(int, int); extern int in_mandel(double x0, double y0, int n); extern int divide(int a, int b, int *remainder); extern double avg(double *a, int n); typedef struct Point { double x,y; } Point; extern double distance(Point *p1, Point *p2);
一旦你寫好了接口文件,就可以在命令行工具中調用Swig了:
bash % swig -python -py3 sample.i
bash %
swig的輸出就是兩個文件,sample_wrap.c和sample.py。
後面的文件就是用戶需要導入的。
而sample_wrap.c文件是需要被編譯到名叫 _sample
的支持模塊的C代碼。
這個可以通過跟普通擴展模塊一樣的技術來完成。
例如,你創建了一個如下所示的 setup.py
文件:
# setup.py
from distutils.core import setup, Extension
setup(name=‘sample‘,
py_modules=[‘sample.py‘],
ext_modules=[
Extension(‘_sample‘,
[‘sample_wrap.c‘],
include_dirs = [],
define_macros = [],
undef_macros = [],
library_dirs = [],
libraries = [‘sample‘]
)
]
)
要編譯和測試,在setup.py上執行python3,如下:
bash % python3 setup.py build_ext --inplace running build_ext building ‘_sample‘ extension gcc -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I/usr/local/include/python3.3m -c sample_wrap.c -o build/temp.macosx-10.6-x86_64-3.3/sample_wrap.o sample_wrap.c: In function ‘SWIG_InitializeModule’: sample_wrap.c:3589: warning: statement with no effect gcc -bundle -undefined dynamic_lookup build/temp.macosx-10.6-x86_64-3.3/sample.o build/temp.macosx-10.6-x86_64-3.3/sample_wrap.o -o _sample.so -lsample bash %
如果一切正常的話,你會發現你就可以很方便的使用生成的C擴展模塊了。例如:
>>> import sample
>>> sample.gcd(42,8)
2
>>> sample.divide(42,8)
[5, 2]
>>> p1 = sample.Point(2,3)
>>> p2 = sample.Point(4,5)
>>> sample.distance(p1,p2)
2.8284271247461903
>>> p1.x
2.0
>>> p1.y
3.0
>>> import array
>>> a = array.array(‘d‘,[1,2,3])
>>> sample.avg(a)
2.0
>>>
討論?
Swig是Python歷史中構建擴展模塊的最古老的工具之一。
Swig能自動化很多包裝生成器的處理。
所有Swig接口都以類似下面這樣的為開頭:
%module sample
%{
#include "sample.h"
%}
這個僅僅只是聲明了擴展模塊的名稱並指定了C頭文件,
為了能讓編譯通過必須要包含這些頭文件(位於 %{ 和 %} 的代碼),
將它們之間復制粘貼到輸出代碼中,這也是你要放置所有包含文件和其他編譯需要的定義的地方。
Swig接口的底下部分是一個C聲明列表,你需要在擴展中包含它。
這通常從頭文件中被復制。在我們的例子中,我們僅僅像下面這樣直接粘貼在頭文件中:
%module sample
%{
#include "sample.h"
%}
...
extern int gcd(int, int);
extern int in_mandel(double x0, double y0, int n);
extern int divide(int a, int b, int *remainder);
extern double avg(double *a, int n);
typedef struct Point {
double x,y;
} Point;
extern double distance(Point *p1, Point *p2);
有一點需要強調的是這些聲明會告訴Swig你想要在Python模塊中包含哪些東西。
通常你需要編輯這個聲明列表或相應的修改下它。
例如,如果你不想某些聲明被包含進來,你要將它從聲明列表中移除掉。
使用Swig最復雜的地方是它能給C代碼提供大量的自定義操作。
這個主題太大,這裏無法展開,但是我們在本節還剩展示了一些自定義的東西。
第一個自定義是 %extend
指令允許方法被附加到已存在的結構體和類定義上。
我例子中,這個被用來添加一個Point結構體的構造器方法。
它可以讓你像下面這樣使用這個結構體:
>>> p1 = sample.Point(2,3)
>>>
如果略過的話,Point對象就必須以更加復雜的方式來被創建:
>>> # Usage if %extend Point is omitted
>>> p1 = sample.Point()
>>> p1.x = 2.0
>>> p1.y = 3
第二個自定義涉及到對 typemaps.i
庫的引入和 %apply
指令,
它會指示Swig參數簽名 int *remainder
要被當做是輸出值。
這個實際上是一個模式匹配規則。
在接下來的所有聲明中,任何時候只要碰上 int *remainder
,他就會被作為輸出。
這個自定義方法可以讓 divide()
函數返回兩個值。
>>> sample.divide(42,8)
[5, 2]
>>>
最後一個涉及到 %typemap
指令的自定義可能是這裏展示的最高級的特性了。
一個typemap就是一個在輸入中特定參數模式的規則。
在本節中,一個typemap被定義為匹配參數模式 (double *a, int n)
.
在typemap內部是一個C代碼片段,它告訴Swig怎樣將一個Python對象轉換為相應的C參數。
本節代碼使用了Python的緩存協議去匹配任何看上去類似雙精度數組的輸入參數
(比如NumPy數組、array模塊創建的數組等),更多請參考15.3小節。
在typemap代碼內部,$1和$2這樣的變量替換會獲取typemap模式的C參數值
(比如$1映射為 double *a
)。$input指向一個作為輸入的 PyObject *
參數,
而 $argnum
就代表參數的個數。
編寫和理解typemaps是使用Swig最基本的前提。
不僅是說代碼更神秘,而且你需要理解Python C API和Swig和它交互的方式。
Swig文檔有更多這方面的細節,可以參考下。
不過,如果你有大量的C代碼需要被暴露為擴展模塊。
Swig是一個非常強大的工具。關鍵點在於Swig是一個處理C聲明的編譯器,
通過強大的模式匹配和自定義組件,可以讓你更改聲明指定和類型處理方式。
更多信息請去查閱 Swig網站 ,
還有 特定於Python的相關文檔
艾伯特(http://www.aibbt.com/)國內第一家人工智能門戶
Python Cookbook(第3版)中文版:15.9 用WSIG包裝C代碼