Python擴(kuò)展方法及工具比較 一、普通擴(kuò)展方法 擴(kuò)展Python包括三個(gè)步驟: 1. 創(chuàng)建源程序(C, C++, java, ...); 2. 為源程序?qū)憌rap代碼; 包括四個(gè)步驟: Ø include "Python.h"; Ø 為每個(gè)模塊函數(shù)寫wrap,即:PyObject* Module_func(); Ø 為每個(gè)模塊函數(shù)寫函數(shù)定義,即:PyMethodDef ModuleMethods[]對(duì)應(yīng)表; Ø 寫模塊的初始化函數(shù):void initModule()部分。 3. 編譯連接; 有兩種方法: (1)使用distutils包。步驟如下: Ø 修改distutils包中的setup.py文件; Ø 根據(jù)需要運(yùn)行$ python setup.py build或$ python setup.py install命令,生成擴(kuò)展模塊的共享庫文件。 (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. 使用擴(kuò)展內(nèi)容 進(jìn)入python環(huán)境,通過import foo,使用foo模塊中的函數(shù)。如用foo.func()調(diào)用foo模塊中的func()函數(shù)。 二、Python的C++擴(kuò)展 通過C++擴(kuò)展python也是可以的,只不過有一些限制。如果主函數(shù)(Python解釋器)被C編譯器編譯和連接,那么構(gòu)造器中不能使用全局和靜態(tài)對(duì)象。但如果使用C++編譯器則沒有這個(gè)問題。另外,將被Python解釋器調(diào)用的函數(shù)(特別是初始化函數(shù)),需要在函數(shù)體外使用extern "C" 聲明。同時(shí)要在Python頭文件處使用extern "C" {...}聲明。 一個(gè)示例程序(wraper文件): extern "C" { #include "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); } 三、使用工具進(jìn)行擴(kuò)展 雖然擴(kuò)展過程并不復(fù)雜,但也可以使用許多已知的工具簡(jiǎn)化擴(kuò)展過程。 (1) SWIG 由David Beazley創(chuàng)建,是一個(gè)自動(dòng)的擴(kuò)展構(gòu)造工具。它讀入注釋的C/C++頭文件,為python、tcl、perl等多種腳本語言產(chǎn)生wrap代碼。SWIG可以包裝大量C++特性到Python的擴(kuò)展模塊中。詳情可參考http://www.。 評(píng)價(jià):swig簡(jiǎn)單,可以支持多種腳本文件,但支持的c++特性不完備。 (2) SIP 由Phil Thompson創(chuàng)建,是一個(gè)C++模塊構(gòu)造器,專門為C++的類創(chuàng)造wrapper。它曾經(jīng)被用于創(chuàng)建PyQt和PyKDE擴(kuò)展模塊,因此比較出名。詳情可參考http://www./sip/。 評(píng)價(jià):支持C++特征很齊全,但比較復(fù)雜。 (3) bgen 該工具被包含在標(biāo)準(zhǔn)Python發(fā)布包中的模塊構(gòu)建工具集里,由Jack Jansen維護(hù)。它用于產(chǎn)生在Macintosh版本可用的Python擴(kuò)展模塊。 (4) pyfort 由Paul dubois創(chuàng)建,用來產(chǎn)生Fortran語言生成的擴(kuò)展模塊。詳見http://pyfortran.。 (5) cxx 也由Paul Dubois創(chuàng)建,是一個(gè)庫,為Python的C++擴(kuò)展提供了友好的API。Cxx允許將許多python對(duì)象(如list和tuple)使用到STL的運(yùn)算中。庫也提供了C++異常處理到python異常處理的轉(zhuǎn)化。詳見http://cxx.。 (6) WrapPy 由Greg Couch創(chuàng)建,通過讀入C++頭文件來產(chǎn)生擴(kuò)展模塊。詳見http://www.cgl./home/gregc/wrappy/index.html。 (7) Boost Python Library 由David Abrahams創(chuàng)建。該庫提供了更多與眾不同的C++ wrap到python擴(kuò)展中,而只需要對(duì)要擴(kuò)展的C++類寫很少的附加信息。詳見http://www./libs/python/doc。 評(píng)價(jià):Boost為C++提供了許多實(shí)用的庫,如Regex(正則表達(dá)式庫)、Graph(圖組件和算法)、concept check(檢查泛型編程中的concept)、Thread(可移植的C++多線程庫)、Python(把C++類和函數(shù)映射到Python之中)、Pool(內(nèi)存池管理)等等。 Boost總體來說是實(shí)用價(jià)值很高,質(zhì)量很高的庫。并且強(qiáng)調(diào)對(duì)跨平臺(tái)的支持。但是Boost中也有很多是實(shí)驗(yàn)性質(zhì)的東西,在實(shí)際的開發(fā)中實(shí)用需要謹(jǐn)慎。 boost.python支持的c++特性較多,但是比較復(fù)雜。 四、擴(kuò)展工具的使用 1. SWIG SWIG可以完成多種腳本語言的C/C++擴(kuò)展,包括python、tcl、perl、CHICKEN、php、XML等等許多。它通過構(gòu)造接口函數(shù)和代理類來實(shí)現(xiàn)模擬。 1) 原理 (1) 接口函數(shù) 實(shí)現(xiàn)一系列接口函數(shù)來隱藏一個(gè)結(jié)構(gòu)體的底層實(shí)現(xiàn)。例如對(duì)結(jié)構(gòu)體: struct Vector { Vector(); ~Vector(); double x,y,z; }; 將被轉(zhuǎn)換為以下的函數(shù)集合: 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); 于是,這些函數(shù)在解釋器中便可以如下使用: % set v [new_Vector] % Vector_x_set $v 3.5 % Vector_y_get $v % delete_Vector $v % ... (2) 代理類 也叫做shadow類,是真實(shí)C++類的代理。使用代理類時(shí),實(shí)際工作的有兩個(gè)對(duì)象——一個(gè)在腳本語言中,另一個(gè)是C/C++的底層對(duì)象。操作同時(shí)影響著兩個(gè)對(duì)象,但用戶看起來只是一個(gè)。例如,如果你有如下C++定義: class Vector { public: Vector(); ~Vector(); double x,y,z; }; 使用了代理類機(jī)制后,將會(huì)用很透明的方式訪問結(jié)構(gòu)。例如在Python中,可直接如下訪問: >>> v = Vector() >>> v.x = 3 >>> v.y = 4 >>> v.z = -13 >>> ... >>> del v 2)支持特性與局限 SWIG當(dāng)前支持以下的C++特性: Ø 類 Ø 類的構(gòu)造和析構(gòu) Ø 虛函數(shù) Ø 公共繼承(包括多重繼承) Ø 靜態(tài)函數(shù) Ø 函數(shù)和方法重載 Ø 大多數(shù)標(biāo)準(zhǔn)運(yùn)算符的重載 Ø 引用 Ø 模板 Ø 函數(shù)指針 Ø 名字空間 雖然SWIG能夠解析大多數(shù)C/C++聲明,但不能提供完備的解析機(jī)制。限制包括一些非常復(fù)雜類型的聲明和C++的高級(jí)特性。下面是目前不被支持的一些特性: Ø 一些非常規(guī)的類型聲明。例如,SWIG不支持以下一些聲明: /* Non-conventional placement of storage specifier (extern) */ const int extern Number; /* Extra declarator grouping */ Matrix (foo); // A global variable /* Extra declarator grouping in parameters */ void bar(Spam (Grok)(Doh)); Ø 直接在C++源碼運(yùn)行SWIG會(huì)有一些問題。雖然SWIG能夠解析C++類聲明,但是當(dāng)它遇到本身不支持的聲明時(shí),會(huì)自動(dòng)跳過。 Ø 某些C++的高級(jí)特性目前不被支持。如: Ø 友元 Ø 私有和保護(hù)成員 Ø 某些操作符的重載(如new、delete等) 3) 使用方法 使用SWIG工具來進(jìn)行Python的C++擴(kuò)展,包括以下幾個(gè)步驟: Ø 編寫C++源代碼; Ø 編寫后綴為.i或者.swg的腳本文件,標(biāo)記頭文件和要擴(kuò)展的類; Ø 編譯連接生成共享庫; Ø 使用擴(kuò)展。 (1) 運(yùn)行SWIG 安裝SWIG成功后,使用以下格式的命令運(yùn)行: $ swig [ options ] filename 選項(xiàng)包括: -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 這只是命令行選項(xiàng)的一個(gè)子集。對(duì)每種目標(biāo)語言都有各自附加的選項(xiàng)??梢允褂妹?swig -help or swig -lang -help"查看全部。 filename是用戶編寫的SWIG標(biāo)記腳本文件。 (2) SWIG的輸入 輸入為編寫的腳本文件,通常后綴為.i或.swg。 通常該腳本文件的格式如下: %module mymodule %{ #include "myheader.h" %} // Now list ANSI C/C++ declarations int foo; int bar(int x); ... 模塊名使用"%module"(或-module命令行選項(xiàng))進(jìn)行標(biāo)記。這個(gè)標(biāo)記必須在文件的開始出現(xiàn),用于命名目標(biāo)擴(kuò)展模塊。如果選擇在命令行提供,則不需要"%module"標(biāo)記。 在"%{ ... %}"中進(jìn)行頭文件和其它特殊的聲明(如% rename、% ignore等)。它將被逐字的復(fù)制到SWIG創(chuàng)建的wrapper文件中。 (3) SWIG的輸出 SWIG的輸出是一系列wrapper文件,也可能根據(jù)目標(biāo)文件的不同產(chǎn)生一些其它的文件。默認(rèn)情況下,輸入名為file.i的文件將輸出文件file_wrap.c或file_wrap.cxx(依賴于是否使用了-c++選項(xiàng))。編譯器通常是通過文件后綴來確定源語言(C、C++等)類型的。輸出文件的名字可以通過-o選項(xiàng)修改。例如: $ swig -c++ -python -o example_wrap.cpp example.i SWIG創(chuàng)建的wrapper文件可直接用來編譯連接產(chǎn)生共享庫,不需要再對(duì)生成文件進(jìn)行編輯。 2. SIP(A Tool for Generating Python Bindings for C and C++ Libraries) Python-SIP是一個(gè)用于為Python生成C++接口的工具。它類似于SWIG,但使用了一個(gè)不同的接口格式。它用于建造PyQt 和PyKDE,支持Qtsignal/slot機(jī)制。 SIP是一個(gè)為C/C++庫自動(dòng)生成Python綁定的工具。SIP最初于1998年為了PyQt(Python綁定到Qt GUI工具集)而開發(fā)的,但也適合于生成C/C++庫的綁定。 SIP的命名是因?yàn)樗畛跏亲鳛橐粋€(gè)小的SWIG出現(xiàn)的。與SWIG不同,SIP實(shí)現(xiàn)是為了盡可能最小化的實(shí)現(xiàn)Python與C/C++的整合。 1)支持特性與局限 SIP的主要優(yōu)點(diǎn)是綁定加載速度快,內(nèi)存消耗小,尤其在只使用一個(gè)大庫中的小集合時(shí)。它支持的特性主要包括: Ø 提供標(biāo)準(zhǔn)Python和C/C++數(shù)據(jù)類型間的自動(dòng)轉(zhuǎn)換; Ø 根據(jù)不同參數(shù)重載函數(shù); Ø 提供對(duì)C++類保護(hù)方法的接口; Ø 可以在Python中定義C++類的子類,包括C++的抽象類; Ø 支持原始的C++函數(shù)、類方法、靜態(tài)類方法、虛類方法和抽象類方法; Ø 可以在Python中重新實(shí)現(xiàn)C++虛方法和抽象方法; Ø 支持全局變量和類變量; Ø 支持C++的名字空間; Ø 支持C++異常,并能將之轉(zhuǎn)換為Python異常; Ø 可以定義C++類和類似的Python數(shù)據(jù)類型之間的映射,并能自動(dòng)調(diào)用; Ø 可以在某特定文件中包括可提取文檔; Ø 可以在特定文件中包括版權(quán)信息,使其自動(dòng)包含到生成的所有源代碼中; Ø 擴(kuò)展過程與特定平臺(tái)無關(guān); Ø SIP也能理解Qt實(shí)現(xiàn)的signal/slot類型安全回調(diào)機(jī)制。 3) 使用方法 (1) 使用步驟: Ø 寫.sip規(guī)范文件; Ø 用命令$ sip -c . foo.sip在當(dāng)前目錄產(chǎn)生C++代碼; Ø 寫configure.py腳本文件,用命令$ python configure.py來生成Makefile文件; Ø 運(yùn)行$ make;make install完成編譯和安裝擴(kuò)展模塊。 (2) 運(yùn)行: SIP命令行語法如下: $ sip [options] [specification] 其中,specification是模塊規(guī)范文件(通常后綴為sip)的文件名。若被省略則默認(rèn)為stdin。 命令行選項(xiàng)如下: -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) 輸入: 輸入為規(guī)范文件。 我們通過一個(gè)簡(jiǎn)單的規(guī)范文件示例來說明規(guī)范文件語法。假定有一個(gè)C++庫實(shí)現(xiàn)了Word類。類有一個(gè)構(gòu)造器,構(gòu)造器以一個(gè)\0結(jié)束的字符串作為唯一參數(shù)。類有一個(gè)叫做reverse()的無參方法,它返回一個(gè)\0結(jié)束的字符串。 類的接口在頭文件word.h中定義,如下所示: // Define the interface to the word library. class Word { const char *the_word; public: Word(const char *w); char *reverse() const; }; 相應(yīng)的SIP規(guī)范文件如下所示: // Define the SIP wrapper to the word library. %Module word 0 class Word { %TypeHeaderCode #include "word.h" %End public: Word(const char *); char *reverse() const; }; SIP 使用指示器(Directives)來進(jìn)行C++特性的映射。指示器主要包括: %AccessCode %CModule 實(shí)現(xiàn)的是C模塊,并定義模塊名稱; %ConvertFromTypeCode 將C/C++類型轉(zhuǎn)換為Python類型; %ConvertToSubClassCode 同上(基于RTTI); %ConvertToTypeCode 同上; %Copying 添加的手寫代碼會(huì)包含到SIP生成的代碼文件頭中; %Doc 可以由命令提取出文檔信息; %End 標(biāo)識(shí)包含代碼或文本塊結(jié)束標(biāo)志; %ExportedDoc 可被import的文檔; %Feature 與%If、% Platforms、%Timeline一起使用,控制規(guī)范文件中一些部分是否被處理; %If %Import 導(dǎo)入其它模塊的規(guī)范文件; %Include 包括其它文件; %License 用來實(shí)現(xiàn)可選的執(zhí)行字典,包括Licensee, Signature, Timestamp和Type注解; %MappedType 定義自動(dòng)類型轉(zhuǎn)換映射表; %MethodCode 全局函數(shù)、類方法、運(yùn)算符、構(gòu)造和解析等的實(shí)現(xiàn)代碼; %Module 實(shí)現(xiàn)的是C++模塊,并定義模塊名稱; %ModuleCode 編寫能夠被其它模塊調(diào)用的函數(shù)代碼; %ModuleHeaderCode 被生成的所有文件包含的函數(shù)體聲明; %OptionalInclude 作用同%Include,但打開出錯(cuò)時(shí)繼續(xù)處理; %Platforms 配合%If,設(shè)置平臺(tái)信息; %PostInitialisationCode 編寫模塊調(diào)入初始化后立即執(zhí)行的代碼; %PreInitialisationCode 編寫模塊調(diào)入初始化前執(zhí)行的代碼; %Timeline 配合%If,設(shè)置版本信息; %TypeCode 標(biāo)注類或結(jié)構(gòu)中的函數(shù),使其可以被其它結(jié)構(gòu)或類調(diào)用; %TypeHeaderCode 定義結(jié)構(gòu)或類中將包含的頭文件,使得頭文件中類型可以被使用; %VirtualCatcherCode 虛函數(shù)實(shí)現(xiàn)相關(guān)的標(biāo)識(shí)。 SIP使用注解(Annotations)來進(jìn)行參數(shù)和函數(shù)的高級(jí)說明。包括參數(shù)注解、類注解、函數(shù)注解、enum注解、license注解和變量注解。注解有自己的類型和相應(yīng)的可選值。舉例如下: 在Python中,函數(shù)參數(shù)類型不匹配時(shí)能夠自動(dòng)調(diào)整為匹配,但在C/C++中將會(huì)出錯(cuò)。若在參數(shù)后添加Constrained注解將會(huì)解決這個(gè)問題。 void foo(double); void foo(int); ================================================ void foo(double /Constrained/); void foo(int); (4) 輸出: 一系列生成文件,供編譯連接成為共享庫。 3. Boost 1)簡(jiǎn)介 Boost是一套開放源代碼、高度可移植的C++庫,由C++標(biāo)準(zhǔn)委員會(huì)庫工作組發(fā)起。主要有以下一些特點(diǎn): Ø 支持正則表達(dá)式和各種字符類型(如char、wchar_t及自定義字符類型); Ø 支持多線程(跨平臺(tái)多線程庫); Ø 支持?jǐn)?shù)據(jù)結(jié)構(gòu)"圖",以及即將加入標(biāo)準(zhǔn)的hash_set、hash_map、hash_multiset、hash_multimap等,C++對(duì)數(shù)據(jù)結(jié)構(gòu)的支持已近完備; Ø 支持Python語言的擴(kuò)展; Ø 智能指針,與std::auto_ptr一起使用,可杜絕內(nèi)存泄露,且高效; Ø 支持循環(huán)冗余的CRC、元組tuple、可容納不同類型值的any等; Ø 還在迅速擴(kuò)大中,部分內(nèi)容有望進(jìn)入C++標(biāo)準(zhǔn)庫。 Boost.Python,一個(gè)C++庫,能夠在C++和Python程序之間無縫連接。而且不需要任何額外的工具——只要你的C++編譯器。不必為了wrap而修改C++代碼,使用簡(jiǎn)單。 當(dāng)前版本已經(jīng)被重寫,具有更靈活方便的接口和新的功能。包括: Ø 引用和指針 Ø Globally Registered Type Coercions Ø 自動(dòng)跨模塊的類型轉(zhuǎn)換 Ø 有效的函數(shù)重載 Ø C++到Python的異常轉(zhuǎn)換 Ø 默認(rèn)參數(shù) Ø Keyword Arguments Ø 在C++中使用Python對(duì)象 Ø Exporting C++ Iterators as Python Iterators Ø Documentation String 2)使用 使用步驟包括: Ø 寫C++源程序; Ø 寫C++ wrapper; Ø 使用bjam對(duì)wrapper進(jìn)行build; Ø 在python中使用。 3)程序舉例: (1) 簡(jiǎn)單的C++函數(shù): char const* greet() { return "hello, world"; } 可以被寫成如下的Boost.Python wrapper: #include using namespace boost::python; BOOST_PYTHON_MODULE(hello) { def("greet", greet); } 然后將它構(gòu)建成共享庫,便在Python中加以使用: >>> import hello >>> print hello.greet() hello, world (2) 類和結(jié)構(gòu) 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文件為: #include 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") /*...*/ ; 五、總結(jié) 本文只是對(duì)幾個(gè)擴(kuò)展工具的簡(jiǎn)單介紹,對(duì)每種工具將在后續(xù)文章中陸續(xù)加以說明,并附以代碼。 |
|