使用C語言擴充Python程式的簡單入門指引

來源:互聯網
上載者:User
一、簡介

Python是一門功能強大的進階指令碼語言,它的強大不僅表現在其自身的功能上,而且還表現在其良好的可擴充性上,正因如此,Python已經開始受到越來越多人的青睞,並且被屢屢成功地應用於各類大型軟體系統的開發過程中。

與其它普通指令碼語言有所不同,Python程式員可以藉助Python語言提供的API,使用C或者C++來對Python進行功能性擴充,從而即可以利用Python方便靈活的文法和功能,又可以獲得與C或者C++幾乎相同的執行效能。執行速度慢是幾乎所有指令碼語言都具有的共性,也是倍受人們指責的一個重要因素,Python則通過與C語言的有機結合巧妙地解決了這一問題,從而使指令碼語言的應用範圍得到了很大擴充。

在用Python開發實際軟體系統時,很多時候都需要使用C/C++來對Python進行擴充。最常見的情況是目前已經存在一個用C編寫的庫,需要在Python語言中使用該庫的某些功能,此時就可以藉助Python提供的擴充功能來實現。此外,由於Python從本質上講還是一種指令碼語言,某些功能用Python實現可能很難滿足實際軟體系統對執行效率的要求,此時也可以藉助Python提供的擴充功能,將這些關鍵程式碼片段用C或者C++實現,從而提供者的執行效能。

本文主要介紹Python提供的C語言擴充介面,以及如何使用這些介面和C/C++語言來對Python進行功能性擴充,並輔以具體的執行個體講述如何?Python的功能擴充。

二、Python的C語言介面

Python是用C語言實現的一種指令碼語言,本身具有優良的開放性和可擴充性,並提供了方便靈活的應用程式介面(API),從而使得C/C++程式員能夠在各個層級上對Python解譯器的功能進行擴充。在使用C/C++對Python進行功能擴充之前,必須首先掌握Python解釋所提供的C語言介面。
2.1 Python對象(PyObject)

Python是一門物件導向的指令碼語言,所有的對象在Python解譯器中都被表示成PyObject,PyObject結構包含Python對象的所有成員指標,並且對Python對象的類型資訊和引用計數進行維護。在進行Python的擴充編程時,一旦要在C或者C++中對Python對象進行處理,就意味著要維護一個PyObject結構。

在Python的C語言擴充介面中,大部分函數都有一個或者多個參數為PyObject指標類型,並且傳回值也大都為PyObject指標。
2.2 引用計數

為了簡化記憶體管理,Python通過引用計數機制實現了自動的記憶體回收功能,Python中的每個對象都有一個引用計數,用來計數該對象在不同場所分別被引用了多少次。每當引用一次Python對象,相應的引用計數就增1,每當消毀一次Python對象,則相應的引用就減1,只有當引用計數為零時,才真正從記憶體中刪除Python對象。

下面的例子說明了Python解譯器如何利用引用計數來對Pyhon對象進行管理:

例1:refcount.py

class refcount:  # etc.r1 = refcount() # 引用計數為1r2 = r1     # 引用計數為2del(r1)     # 引用計數為1del(r2)     # 引用計數為0,刪除對象

在C/C++中處理Python對象時,對引用計數進行正確的維護是一個關鍵問題,處理不好將很容易產生記憶體流失。Python的C語言介面提供了一些宏來對引用計數進行維護,最常見的是用Py_INCREF()來增加使Python對象的引用計數增1,用Py_DECREF()來使Python對象的引用計數減1。
2.3 資料類型

Python定義了六種資料類型:整型、浮點型、字串、元組、列表和字典,在使用C語言對Python進行功能擴充時,首先要瞭解如何在C和Python的資料類型間進行轉化。

2.3.1 整型、浮點型和字串

在Python的C語言擴充中要用到整型、浮點型和字串這三種資料類型時相對比較簡單,只需要知道如何產生和維護它們就可以了。下面的例子給出了如何在C語言中使用Python的這三種資料類型:

例2:typeifs.c

// build an integerPyObject* pInt = Py_BuildValue("i", 2003);assert(PyInt_Check(pInt));int i = PyInt_AsLong(pInt);Py_DECREF(pInt);// build a floatPyObject* pFloat = Py_BuildValue("f", 3.14f);assert(PyFloat_Check(pFloat));float f = PyFloat_AsDouble(pFloat);Py_DECREF(pFloat);// build a stringPyObject* pString = Py_BuildValue("s", "Python");assert(PyString_Check(pString);int nLen = PyString_Size(pString);char* s = PyString_AsString(pString);Py_DECREF(pString);

2.3.2 元組

Python語言中的元組是一個長度固定的數組,當Python解譯器調用C語言擴充中的方法時,所有非關鍵字(non-keyword)參數都以元組方式進行傳遞。下面的例子示範了如何在C語言中使用Python的元群組類型:

例3:typetuple.c

// create the tuplePyObject* pTuple = PyTuple_New(3);assert(PyTuple_Check(pTuple));assert(PyTuple_Size(pTuple) == 3);// set the itemPyTuple_SetItem(pTuple, 0, Py_BuildValue("i", 2003));PyTuple_SetItem(pTuple, 1, Py_BuildValue("f", 3.14f));PyTuple_SetItem(pTuple, 2, Py_BuildValue("s", "Python"));// parse tuple itemsint i;float f;char *s;if (!PyArg_ParseTuple(pTuple, "ifs", &i, &f, &s))  PyErr_SetString(PyExc_TypeError, "invalid parameter");// cleanupPy_DECREF(pTuple);

2.3.3 列表

Python語言中的列表是一個長度可變的數組,列表比元組更為靈活,使用列表可以對其儲存的Python對象進行隨機訪問。下面的例子示範了如何在C語言中使用Python的清單類型:

例4:typelist.c

// create the listPyObject* pList = PyList_New(3); // new referenceassert(PyList_Check(pList));// set some initial valuesfor(int i = 0; i < 3; ++i)  PyList_SetItem(pList, i, Py_BuildValue("i", i));// insert an itemPyList_Insert(pList, 2, Py_BuildValue("s", "inserted"));// append an itemPyList_Append(pList, Py_BuildValue("s", "appended"));// sort the listPyList_Sort(pList);// reverse the listPyList_Reverse(pList);// fetch and manipulate a list slicePyObject* pSlice = PyList_GetSlice(pList, 2, 4); // new referencefor(int j = 0; j < PyList_Size(pSlice); ++j) { PyObject *pValue = PyList_GetItem(pList, j); assert(pValue);}Py_DECREF(pSlice);// cleanupPy_DECREF(pList);

2.3.4 字典

Python語言中的字典是一個根據關鍵字進行訪問的資料類型。下面的例子示範了如何在C語言中使用Python的字典類型:

例5:typedic.c

// create the dictionaryPyObject* pDict = PyDict_New(); // new referenceassert(PyDict_Check(pDict));// add a few named valuesPyDict_SetItemString(pDict, "first",            Py_BuildValue("i", 2003));PyDict_SetItemString(pDict, "second",            Py_BuildValue("f", 3.14f));// enumerate all named valuesPyObject* pKeys = PyDict_Keys(); // new referencefor(int i = 0; i < PyList_Size(pKeys); ++i) { PyObject *pKey = PyList_GetItem(pKeys, i); PyObject *pValue = PyDict_GetItem(pDict, pKey); assert(pValue);}Py_DECREF(pKeys);// remove a named valuePyDict_DelItemString(pDict, "second");// cleanupPy_DECREF(pDict);

三、Python的C語言擴充
3.1 模組封裝

在瞭解了Python的C語言介面後,就可以利用Python解譯器提供的這些介面來編寫Python的C語言擴充,假設有如下一個C語言函數:

例6:example.c

int fact(int n){ if (n <= 1)   return 1; else   return n * fact(n - 1);}

該函數的功能是計算某個給定自然數的階乘,如果想在Python解譯器中調用該函數,則應該首先將其實現為Python中的一個模組,這需要編寫相應的封裝介面,如下所示:

例7: wrap.c

#include PyObject* wrap_fact(PyObject* self, PyObject* args) { int n, result;  if (! PyArg_ParseTuple(args, "i:fact", &n))  return NULL; result = fact(n); return Py_BuildValue("i", result);}static PyMethodDef exampleMethods[] = { {"fact", wrap_fact, METH_VARARGS, "Caculate N!"}, {NULL, NULL}};void initexample() { PyObject* m; m = Py_InitModule("example", exampleMethods);}

一個典型的Python擴充模組至少應該包含三個部分:匯出函數、方法列表和初始化函數。
3.2 匯出函數

要在Python解譯器中使用C語言中的某個函數,首先要為其編寫相應的匯出函數,上述例子中的匯出函數為wrap_fact。在Python的C語言擴充中,所有的匯出函數都具有相同的函數原型:

PyObject* method(PyObject* self, PyObject* args);

該函數是Python解譯器和C函數進行互動的介面,帶有兩個參數:self和args。參數self只在C函數被實現為內聯方法(built-in method)時才被用到,通常該參數的值為空白(NULL)。參數args中包含了Python解譯器要傳遞給C函數的所有參數,通常使用Python的C語言擴充介面提供的函數PyArg_ParseTuple()來獲得這些參數值。

所有的匯出函數都返回一個PyObject指標,如果對應的C函數沒有真正的傳回值(即傳回值類型為void),則應返回一個全域的None對象(Py_None),並將其引用計數增1,如下所示:

PyObject* method(PyObject *self, PyObject *args) { Py_INCREF(Py_None); return Py_None;}

3.3 方法列表

方法列表中給出了所有可以被Python解譯器使用的方法,上述例子對應的方法列表為:

static PyMethodDef exampleMethods[] = { {"fact", wrap_fact, METH_VARARGS, "Caculate N!"}, {NULL, NULL}};

方法列表中的每項由四個部分組成:方法名、匯出函數、參數傳遞方式和方法描述。方法名是從Python解譯器中調用該方法時所使用的名字。參數傳遞方式則規定了Python向C函數傳遞參數的具體形式,可選的兩種方式是METH_VARARGS和METH_KEYWORDS,其中METH_VARARGS是參數傳遞的標準形式,它通過Python的元組在Python解譯器和C函數之間傳遞參數,若採用METH_KEYWORD方式,則Python解譯器和C函數之間將通過Python的字典類型在兩者之間進行參數傳遞。
3.4 初始化函數

所有的Python擴充模組都必須要有一個初始化函數,以便Python解譯器能夠對模組進行正確的初始化。Python解譯器規定所有的初始化函數的函數名都必須以init開頭,並加上模組的名字。對於模組example來說,則相應的初始化函數為:

void initexample() { PyObject* m; m = Py_InitModule("example", exampleMethods);}
當Python解譯器需要匯入該模組時,將根據該模組的名稱尋找相應的初始化函數,一旦找到則調用該函數進行相應的初始化工作,初始化函數則通過調用Python的C語言擴充介面所提供的函數Py_InitModule(),來向Python解譯器註冊該模組中所有可以用到的方法。
3.5 編譯連結

要在Python解譯器中使用C語言編寫的擴充模組,必須將其編譯成動態連結程式庫的形式。下面以RedHat Linux 8.0為例,介紹如何將C編寫的Python擴充模組編譯成動態連結程式庫:

[xiaowp@gary code]$ gcc -fpic -c -I/usr/include/python2.2 \          -I /usr/lib/python2.2/config \          example.c wrapper.c[xiaowp@gary code]$ gcc -shared -o example.so example.o wrapper.o

3.6 引入Python解譯器

當產生Python擴充模組的動態連結程式庫後,就可以在Python解譯器中使用該擴充模組了,與Python內建的模組一樣,擴充模組也是通過import命令引入後再使用的,如下所示:

[xiaowp@gary code]$ pythonPython 2.2.1 (#1, Aug 30 2002, 12:15:30)[GCC 3.2 20020822 (Red Hat Linux Rawhide 3.2-4)] on linux2Type "help", "copyright", "credits" or "license" for more information.>>> import example>>> example.fact(4)24>>>

四、結束語

作為一門功能強大的指令碼語言,Python將被更加廣泛地應用於各個領域。為了克服指令碼語言執行速度慢的問題,Python提供了相應的C語言擴充介面,通過將影響執行效能的關鍵代碼用C語言實現,可以很大程度上提高用Python編寫的指令碼在運行時的速度,從而滿足實際需要。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.