使用C++擴充Python的功能
環境
VS2005Python2.5.4 Windows7(32位)
簡介
長話短說,這裡說的擴充Python功能與直接用其它語言寫一個動態連結程式庫,然後讓Python來調用有點不一樣(雖然本質是一樣的)。而是指使用Python本身提供的API,使用C++來對Python進行功能性擴充,可以這樣理解,使用更高效的語言實現一些演算法計算等等需要更高執行效率的核心(或者需要與系統進行密切互動的)模組,然後讓Python像調用內建標準庫的方式來調用這些模組,聽起來是不是很誘人。。在軟體技術高速發展的今天,藉助幾種電腦語言來實現一個系統的例子數不勝數,目的不外乎就是效能和便利的平衡。譬如本文要討論的使用C++來擴充Python就是Python和C++的一種巧妙的有機結合,好處不言而喻,既可以獲得和C++相似的執行效能,又可以利用Python的開發靈活性。由於Python本身是使用C實現的,二者結合起來還是比較容易的。
基本流程
本文不適合這樣的讀者——對Python完全不瞭解或者對C\C++完全不瞭解,道理你們懂的。另外就是Python裡面有6種基礎資料型別 (Elementary Data Type)。你需要瞭解如何在C和Python之間對這些類型進行轉化(這不在本文討論範圍,可以參考[1])。
言歸正傳,感覺前面說得太多了,實際上很簡單,因此我決定少說多做。一個C++的Python擴充模組至少應該有匯出函數,方法列表和初始化函數三個部分。我們用VS2005這個強大的工具開工。一般來說,你應該建一個Dll工程(至於使用exe來擴充Python可以不可以,暫時還沒研究過)。下面按部就班的說明(關鍵說明在注釋部分)。
一、初始化函數
//-------------------------------------------------------------------------
// 函數 : initPyExt
// 功能 : 初始化函數
// 傳回值 :PyMODINIT_FUNC
// 附註 : 注意,這個函數的名字不能改動。必須是init+模組名字,
// 我們的模組名字是PyExt,所以函數名是initPyExt。Python在匯入
// 我們的PyExt模組時,會找到這個函數,並調用。這個函數實現的
// 功能很簡單,通過調用Py_InitModule將模組名字和映射表結合起
// 來,它的意思是說PyExt這個模組使用PyExtMethods這個映射表。
//-------------------------------------------------------------------------
PyMODINIT_FUNCinitPyExt()
{
Py_InitModule("PyExt",PyExtMethods);
}
二、方法列表
/*
方法列表,這個是一個C結構數組。把需要擴充的函數都映射到這個表裡。
那麼Python就知道你的這個擴充模組支援一些什麼方法了。表的第一個字
段是方法名字,也是通過Python來調用時的名字。第二個欄位是匯出函數,
是真正調用的函數,也是C\C++實現的函數。第三個參數是指明Python向
C\C++函數傳遞參數的形式。可選的兩種方式是METH_VARARGS和
METH_KEYWORDS,其中METH_VARARGS是參數傳遞的標準形式,它通
過Python的元組在Python解譯器和C函數之間傳遞參數,若採用
METH_KEYWORD方式,則Python解譯器和C函數之間將通過Python的字典
類型在兩者之間進行參數傳遞。第四個欄位是這個函數的說明。如果你在
python裡來help這個函數,將顯示這個說明。相當於在python裡的函數的文檔說明。
*/
staticPyMethodDefPyExtMethods[]=
{
{"Add", Add,METH_VARARGS,"Addtwo number - edit by magictong."},
{"ExecSystem",ExecSystem,METH_VARARGS,"Execute a shell command - edit bymagictong." },
{NULL,NULL, 0,NULL}
};
三、匯出函數
//-------------------------------------------------------------------------
// 函數 : Add
// 功能 : 這是一個加法函數
// 傳回值 :PyObject*
// 參數 : PyObject*self 這個參數我們暫時不用理會
// 參數 : PyObject*args 是一個參數列表,我們需要從它解析出參數
// 附註 :
// 所有的匯出函數都具有相同的原型:
// PyObject*method(PyObject* self, PyObject* args);
//PyArg_ParseTuple來完成解析參數任務。它的第一個參數是args,
// 就是我們要轉換的參數。第二個是格式符號。"s"代表是個string。
// 從args裡提取一個參數就寫"s",兩個的話就寫"s|s",如果是一個
// string,一個int,就寫"s|i",有點和printf類似哦。第三個參數就是
// 提取出來的參數放置的真正位置。必須傳遞這個參數的地址。
//-------------------------------------------------------------------------
staticPyObject*Add(PyObject*self,PyObject*args)
{
intx = 0 ;
inty = 0;
intz = 0;
if(!PyArg_ParseTuple(args,"i|i", &x, &y))
returnNULL;
z=x +y;
returnPy_BuildValue("i",z);
/*
調用完之後我們需要返回結果。這個結果是c的type或者是我們自己定義的類型。
必須把他轉換成PyObject,讓python認識。這個用Py_BuildValue來完成。他
是PyArg_ParseTuple的逆過程。他的第一個參數和PyArg_ParseTuple的第二個
參數一樣,是個格式化符號。第三個參數是我們需要轉換的參數。Py_BuildValue
會把所有的返回只組裝成一個tutple給python。
如果對應的C函數沒有傳回值(即傳回值類型為void),則應返回一個全域的None
對象(Py_None),並將其引用計數增,如下所示:
Py_INCREF(Py_None);
returnPy_None;
*/
}
四、再加點功能
intcmd(constchar* arg)
{
returnsystem(arg);
}
staticPyObject*ExecSystem(PyObject*self,PyObject*args)
{
constchar*command;
if(!PyArg_ParseTuple(args,"s", &command))
returnNULL;
intn =cmd(command);
returnPy_BuildValue("i",n);
}
編譯
開編,編譯出來的PyExt.dll檔案改名為PyExt.pyd放入Python的C:\Python25\DLLs目錄就可以全域使用了,如果你只想某個Python的工程,放在工程的相對路徑下面就可以了。
使用
可能的問題
裡面的這些PyMODINIT_FUNC,與Python相關的宏和定義在哪裡呢。定義下