1. 程式人生 > >Python Cookbook(第3版)中文版:15.9 用WSIG包裝C代碼

Python Cookbook(第3版)中文版:15.9 用WSIG包裝C代碼

轉換 -i 代碼 第3版 deb mac 變量 cor bundle

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代碼