天天看點

Python擴充方法及工具比較

http://zfqcn.blog.163.com/blog/static/2280681200711111311923/

  一、普通擴充方法

  擴充Python包括三個步驟:

1. 建立源程式(C, C++, java, ...);

2. 為源程式寫wrap代碼;

  包括四個步驟:

?   include "Python.h";

?   為每個子產品函數寫wrap,即:PyObject* Module_func();

?   為每個子產品函數寫函數定義,即:PyMethodDef ModuleMethods[]對應表;

?   寫子產品的初始化函數:void initModule()部分。

3. 編譯連接配接;

  有兩種方法:

(1)使用distutils包。步驟如下:

?   修改distutils包中的setup.py檔案;

?   根據需要運作$ python setup.py build或$ python setup.py install指令,生成擴充子產品的共享庫檔案。

(2)直接使用gcc指令将子產品編譯成共享庫。指令如下:

$ gcc -fpic -c -I/usr/include/python2.2 -I/usr/lib/python2.2/config foo.c wrap_foo.c

$ gcc -shared -o foo.so foo.o wrap_foo.o

  這樣便生成了python可用的foo子產品。

4. 使用擴充内容

  進入python環境,通過import foo,使用foo子產品中的函數。如用foo.func()調用foo子產品中的func()函數。

二、Python的C++擴充

通過C++擴充python也是可以的,隻不過有一些限制。如果主函數(Python解釋器)被C編譯器編譯和連接配接,那麼構造器中不能使用全局和靜态對

象。但如果使用C++編譯器則沒有這個問題。另外,将被Python解釋器調用的函數(特别是初始化函數),需要在函數體外使用extern "C"

聲明。同時要在Python頭檔案處使用extern "C" {...}聲明。

  一個示例程式(wraper檔案):

extern "C"

{

#i nclude "Python.h"

}

PyObject *cppextest_print_logo(PyObject *self, PyObject *args)

{

    char *string;

    if (!PyArg_ParseTuple(args, "s", &string))   return NULL;

    return Py_None;

}

static PyMethodDef

cppextestMethods[] = {

    {"print_logo", cppextest_print_logo, METH_VARARGS},

    {NULL, NULL},

};

extern "C"

void initcppextest(void)

{

    Py_InitModule("cppextest", cppextestMethods);

}

三、使用工具進行擴充

  雖然擴充過程并不複雜,但也可以使用許多已知的工具簡化擴充過程。

(1) SWIG

  由David Beazley建立,是一個自動的擴充構造工具。它讀入注釋的C/C++頭檔案,為python、tcl、perl等多種腳本語言産生wrap代碼。SWIG可以包裝大量C++特性到Python的擴充子產品中。詳情可參考http://www.swig.org。

  評價:swig簡單,可以支援多種腳本檔案,但支援的c++特性不完備。

(2) SIP

  由Phil Thompson建立,是一個C++子產品構造器,專門為C++的類創造wrapper。它曾經被用于建立PyQt和PyKDE擴充子產品,是以比較出名。詳情可參考http://www.riverbankcomputing.co.uk/sip/。

  評價:支援C++特征很齊全,但比較複雜。

(3) bgen

  該工具被包含在标準Python釋出包中的子產品建構工具集裡,由Jack Jansen維護。它用于産生在Macintosh版本可用的Python擴充子產品。

(4) pyfort

  由Paul dubois建立,用來産生Fortran語言生成的擴充子產品。詳見http://pyfortran.sourceforge.net。

(5) cxx

  也由Paul Dubois建立,是一個庫,為Python的C++擴充提供了友好的API。Cxx允許将許多python對象(如list和tuple)使用到STL的運算中。庫也提供了C++異常處理到python異常處理的轉化。詳見http://cxx.sourceforge.net。

(6) WrapPy

  由Greg Couch建立,通過讀入C++頭檔案來産生擴充子產品。詳見http://www.cgl.ucsf.edu/home/gregc/wrappy/index.html。

(7) Boost Python Library

  由David Abrahams建立。該庫提供了更多與衆不同的C++ wrap到python擴充中,而隻需要對要擴充的C++類寫很少的附加資訊。詳見http://www.boost.org/libs/python/doc。

評價:Boost為C++提供了許多實用的庫,如Regex(正規表達式庫)、Graph(圖元件和算法)、concept

check(檢查泛型程式設計中的concept)、Thread(可移植的C++多線程庫)、Python(把C++類和函數映射到Python之中)、

Pool(記憶體池管理)等等。

  Boost總體來說是實用價值很高,品質很高的庫。并且強調對跨平台的支援。但是Boost中也有很多是實驗性質的東西,在實際的開發中實用需要謹慎。

  boost.python支援的c++特性較多,但是比較複雜。

四、擴充工具的使用

1. SWIG

  SWIG可以完成多種腳本語言的C/C++擴充,包括python、tcl、perl、CHICKEN、php、XML等等許多。它通過構造接口函數和代理類來實作模拟。

1) 原理

(1) 接口函數

  實作一系列接口函數來隐藏一個結構體的底層實作。例如對結構體:

struct Vector {

    Vector();

    ~Vector();

    double x,y,z;

};

将被轉換為以下的函數集合:

Vector *new_Vector();

void delete_Vector(Vector *v);

double Vector_x_get(Vector *v);

double Vector_y_get(Vector *v);

double Vector_y_get(Vector *v);

void Vector_x_set(Vector *v, double x);

void Vector_y_set(Vector *v, double y);

void Vector_z_set(Vector *v, double z);

  于是,這些函數在解釋器中便可以如下使用:

% set v [new_Vector]

% Vector_x_set $v 3.5

% Vector_y_get $v

% delete_Vector $v

% ...

(2) 代理類

  也叫做shadow類,是真實C++類的代理。使用代理類時,實際工作的有兩個對象——一個在腳本語言中,另一個是C/C++的底層對象。操作同時影響着兩個對象,但使用者看起來隻是一個。例如,如果你有如下C++定義:

class Vector {

public:

Vector();

~Vector();

double x,y,z;

};

  使用了代理類機制後,将會用很透明的方式通路結構。例如在Python中,可直接如下通路:

>>> v = Vector()

>>> v.x = 3

>>> v.y = 4

>>> v.z = -13

>>> ...

>>> del v

2)支援特性與局限

  SWIG目前支援以下的C++特性:

?   類

?   類的構造和析構

?   虛函數

?   公共繼承(包括多重繼承)

?   靜态函數

?   函數和方法重載

?   大多數标準運算符的重載

?   引用

?   模闆

?   函數指針

?   名字空間

  雖然SWIG能夠解析大多數C/C++聲明,但不能提供完備的解析機制。限制包括一些非常複雜類型的聲明和C++的進階特性。下面是目前不被支援的一些特性:

?   一些非正常的類型聲明。例如,SWIG不支援以下一些聲明:

const int extern Number;

Matrix (foo);   // A global variable

void bar(Spam (Grok)(Doh));

?   直接在C++源碼運作SWIG會有一些問題。雖然SWIG能夠解析C++類聲明,但是當它遇到本身不支援的聲明時,會自動跳過。

?   某些C++的進階特性目前不被支援。如:

?   友元

?   私有和保護成員

?   某些操作符的重載(如new、delete等)

3) 使用方法

  使用SWIG工具來進行Python的C++擴充,包括以下幾個步驟:

?   編寫C++源代碼;

?   編寫字尾為.i或者.swg的腳本檔案,标記頭檔案和要擴充的類;

?   編譯連接配接生成共享庫;

?   使用擴充。

(1) 運作SWIG

  安裝SWIG成功後,使用以下格式的指令運作:

$ swig [ options ] filename

  選項包括:

-chicken         Generate CHICKEN wrappers

-csharp           Generate C# wrappers

-guile           Generate Guile wrappers

-java           Generate Java wrappers

-mzscheme         Generate Mzscheme wrappers

-ocaml           Generate Ocaml wrappers

-perl           Generate Perl wrappers

-php             Generate PHP wrappers

-pike           Generate Pike wrappers

-python           Generate Python wrappers

-ruby           Generate Ruby wrappers

-sexp           Generate Lisp S-Expressions wrappers

-tcl             Generate Tcl wrappers

-xml             Generate XML wrappers

-c++             Enable C++ parsing

-Dsymbol         Define a preprocessor symbol

-Fstandard         Display error/warning messages in commonly used format

-Fmicrosoft       Display error/warning messages in Microsoft format

-help           Display all options

-Idir           Add a directory to the file include path

-lfile           Include a SWIG library file.

-module name       Set the name of the SWIG module

-o outfile         Name of output file

-outdir dir       Set language specific files output directory

-swiglib         Show location of SWIG library

-version         Show SWIG version number

  這隻是指令行選項的一個子集。對每種目智語言都有各自附加的選項。可以使用指令"swig -help or swig -lang -help"檢視全部。

  filename是使用者編寫的SWIG标記腳本檔案。

(2) SWIG的輸入

  輸入為編寫的腳本檔案,通常字尾為.i或.swg。

  通常該腳本檔案的格式如下:

%module mymodule

%{

#i nclude "myheader.h"

%}

// Now list ANSI C/C++ declarations

int foo;

int bar(int x);

...

  子產品名使用"%module"(或-module指令行選項)進行标記。這個标記必須在檔案的開始出現,用于命名目标擴充子產品。如果選擇在指令行提供,則不需要"%module"标記。

  在"%{ ... %}"中進行頭檔案和其它特殊的聲明(如% rename、% ignore等)。它将被逐字的複制到SWIG建立的wrapper檔案中。

(3) SWIG的輸出

SWIG的輸出是一系列wrapper檔案,也可能根據目标檔案的不同産生一些其它的檔案。預設情況下,輸入名為file.i的檔案将輸出檔案

file_wrap.c或file_wrap.cxx(依賴于是否使用了-c++選項)。編譯器通常是通過檔案字尾來确定源語言(C、C++等)類型的。

輸出檔案的名字可以通過-o選項修改。例如:

$ swig -c++ -python -o example_wrap.cpp example.i

  SWIG建立的wrapper檔案可直接用來編譯連接配接産生共享庫,不需要再對生成檔案進行編輯。

2. SIP(A Tool for Generating Python Bindings for C and C++ Libraries)

  Python-SIP是一個用于為Python生成C++接口的工具。它類似于SWIG,但使用了一個不同的接口格式。它用于建造PyQt 和PyKDE,支援Qtsignal/slot機制。

  SIP是一個為C/C++庫自動生成Python綁定的工具。SIP最初于1998年為了PyQt(Python綁定到Qt GUI工具集)而開發的,但也适合于生成C/C++庫的綁定。

  SIP的命名是因為它最初是作為一個小的SWIG出現的。與SWIG不同,SIP實作是為了盡可能最小化的實作Python與C/C++的整合。

1)支援特性與局限

  SIP的主要優點是綁定加載速度快,記憶體消耗小,尤其在隻使用一個大庫中的小集合時。它支援的特性主要包括:

?   提供标準Python和C/C++資料類型間的自動轉換;

?   根據不同參數重載函數;

?   提供對C++類保護方法的接口;

?   可以在Python中定義C++類的子類,包括C++的抽象類;

?   支援原始的C++函數、類方法、靜态類方法、虛類方法和抽象類方法;

?   可以在Python中重新實作C++虛方法和抽象方法;

?   支援全局變量和類變量;

?   支援C++的名字空間;

?   支援C++異常,并能将之轉換為Python異常;

?   可以定義C++類和類似的Python資料類型之間的映射,并能自動調用;

?   可以在某特定檔案中包括可提取文檔;

?   可以在特定檔案中包括版權資訊,使其自動包含到生成的所有源代碼中;

?   擴充過程與特定平台無關;

?   SIP也能了解Qt實作的signal/slot類型安全回調機制。

3) 使用方法

(1) 使用步驟:

?   寫.sip規範檔案;

?   用指令$ sip -c . foo.sip在目前目錄産生C++代碼;

?   寫configure.py腳本檔案,用指令$ python configure.py來生成Makefile檔案;

?   運作$ make;make install完成編譯和安裝擴充子產品。

(2) 運作:

  SIP指令行文法如下:

$ sip [options] [specification]

  其中,specification是子產品規範檔案(通常字尾為sip)的檔案名。若被省略則預設為stdin。

  指令行選項如下:

-h

Display a help message.

-V

Display the SIP version number.

-a file

The

name of the Scintilla API file to generate. This file contains a

description of the module API in a form that the Scintilla editor

component can use for auto-completion and call tips. By default the

file is not generated.

-b file

The name of the build file to

generate. This file contains the information about the module needed by

the SIP build system to generate a platform and compiler specific

Makefile for the module. By default the file is not generated.

-c dir

The

name of the directory (which must exist) into which all of the

generated C or C++ code is placed. By default no code is generated.

-d file

The

name of the documentation file to generate. Documentation is included

in specification files using the %Doc and %ExportedDoc directives. By

default the file is not generated.

-e

Support for C++

exceptions is enabled. The causes all calls to C++ code to be enclosed

in try/catch blocks and C++ exceptions to be converted to Python

exceptions. By default exception support is disabled.

-I dir

The

directory is added to the list of directories searched when looking for

a specification file given in an %Include or %Import directive. This

option may be given any number of times.

-j number

The

generated code is split into the given number of files. This make it

easier to use the parallel build facility of most modern

implementations of make. By default 1 file is generated for each C

structure or C++ class.

-r

Debugging statements that trace

the execution of the bindings are automatically generated. By default

the statements are not generated.

-s suffix

The suffix to use for generated C or C++ source files. By default .c is used for C and .cpp for C++.

-t tag

The

SIP version tag (declared using a %Timeline directive) or the SIP

platform tag (declared using the %Platforms directive) to generate code

for. This option may be given any number of times so long as the tags

do not conflict.

-w

The display of warning messages is enabled. By default warning messages are disabled.

-x feature

The feature (declared using the %Feature directive) is disabled.

-z file

The name of a file containing more command line options.

(3) 輸入:

  輸入為規範檔案。

  我們通過一個簡單的規範檔案示例來說明規範檔案文法。假定有一個C++庫實作了Word類。類有一個構造器,構造器以一個/0結束的字元串作為唯一參數。類有一個叫做reverse()的無參方法,它傳回一個/0結束的字元串。

  類的接口在頭檔案word.h中定義,如下所示:

// Define the interface to the word library.

class Word {

  const char *the_word;

public:

  Word(const char *w);

  char *reverse() const;

};

  相應的SIP規範檔案如下所示:

// Define the SIP wrapper to the word library.

%Module word 0

class Word {

%TypeHeaderCode

#i nclude "word.h"

%End

public:

  Word(const char *);

  char *reverse() const;

};

  SIP 使用訓示器(Directives)來進行C++特性的映射。訓示器主要包括:

%AccessCode

%CModule 實作的是C子產品,并定義子產品名稱;

%ConvertFromTypeCode 将C/C++類型轉換為Python類型;

%ConvertToSubClassCode 同上(基于RTTI);

%ConvertToTypeCode 同上;

%Copying 添加的手寫代碼會包含到SIP生成的代碼檔案頭中;

%Doc   可以由指令提取出文檔資訊;

%End   辨別包含代碼或文本塊結束标志;

%ExportedDoc 可被import的文檔;

%Feature 與%If、% Platforms、%Timeline一起使用,控制規範檔案中一些部分是否被處理;

%If

%Import   導入其它子產品的規範檔案;

%Include 包括其它檔案;

%License 用來實作可選的執行字典,包括Licensee, Signature, Timestamp和Type注解;

%MappedType 定義自動類型轉換映射表;

%MethodCode 全局函數、類方法、運算符、構造和解析等的實作代碼;

%Module   實作的是C++子產品,并定義子產品名稱;

%ModuleCode 編寫能夠被其它子產品調用的函數代碼;

%ModuleHeaderCode 被生成的所有檔案包含的函數體聲明;

%OptionalInclude 作用同%Include,但打開出錯時繼續處理;

%Platforms 配合%If,設定平台資訊;

%PostInitialisationCode 編寫子產品調入初始化後立即執行的代碼;

%PreInitialisationCode 編寫子產品調入初始化前執行的代碼;

%Timeline 配合%If,設定版本資訊;

%TypeCode 标注類或結構中的函數,使其可以被其它結構或類調用;

%TypeHeaderCode 定義結構或類中将包含的頭檔案,使得頭檔案中類型可以被使用;

%VirtualCatcherCode 虛函數實作相關的辨別。

  SIP使用注解(Annotations)來進行參數和函數的進階說明。包括參數注解、類注解、函數注解、enum注解、license注解和變量注解。注解有自己的類型和相應的可選值。舉例如下:

  在Python中,函數參數類型不比對時能夠自動調整為比對,但在C/C++中将會出錯。若在參數後添加Constrained注解将會解決這個問題。

void foo(double);

void foo(int);

================================================

void foo(double /Constrained/);

void foo(int);

(4) 輸出:

  一系列生成檔案,供編譯連接配接成為共享庫。

3. Boost

1)簡介

  Boost是一套開放源代碼、高度可移植的C++庫,由C++标準委員會庫工作組發起。主要有以下一些特點:

?   支援正規表達式和各種字元類型(如char、wchar_t及自定義字元類型);

?   支援多線程(跨平台多線程庫);

?   支援資料結構"圖",以及即将加入标準的hash_set、hash_map、hash_multiset、hash_multimap等,C++對資料結構的支援已近完備;

?   支援Python語言的擴充;

?   智能指針,與std::auto_ptr一起使用,可杜絕記憶體洩露,且高效;

?   支援循環備援的CRC、元組tuple、可容納不同類型值的any等;

?   還在迅速擴大中,部分内容有望進入C++标準庫。

  Boost.Python,一個C++庫,能夠在C++和Python程式之間無縫連接配接。而且不需要任何額外的工具——隻要你的C++編譯器。不必為了wrap而修改C++代碼,使用簡單。

  目前版本已經被重寫,具有更靈活友善的接口和新的功能。包括:

?   引用和指針

?   Globally Registered Type Coercions

?   自動跨子產品的類型轉換

?   有效的函數重載

?   C++到Python的異常轉換

?   預設參數

?   Keyword Arguments

?   在C++中使用Python對象

?   Exporting C++ Iterators as Python Iterators

?   Documentation String

2)使用

  使用步驟包括:

?   寫C++源程式;

?   寫C++ wrapper;

?   使用bjam對wrapper進行build;

?   在python中使用。

3)程式舉例:

(1) 簡單的C++函數:

char const* greet()

{

return "hello, world";

}

  可以被寫成如下的Boost.Python wrapper:

#i nclude

using namespace boost::python;

BOOST_PYTHON_MODULE(hello)

{

def("greet", greet);

}

  然後将它建構成共享庫,便在Python中加以使用:

>>> import hello

>>> print hello.greet()

hello, world

(2) 類和結構

struct World

{

World(std::string msg): msg(msg) {} // added constructor

void set(std::string msg) { this->msg = msg; }

std::string greet() { return msg; }

std::string msg;

};

  它的wrapper檔案為:

#i nclude

using namespace boost::python;

BOOST_PYTHON_MODULE(hello)

{

class_("World", init())

.def("greet", &World::greet)

.def("set", &World::set)

;

}

(3) 類的繼承

struct Base { virtual ~Base(); };

struct Derived : Base {};

  它的wrapper檔案為:

class_("Base")

;

class_ >("Derived")

;

五、總結

  本文隻是對幾個擴充工具的簡單介紹,對每種工具将在後續文章中陸續加以說明,并附以代碼。