若是你知道如何用C語言編程,那麼爲Python添加新的內置模塊是很容易的。這種擴展模塊能夠作兩件不能直接在Python中完成的事情:它們能夠實現新的內置對象類型,以及調用C庫函數和系統調用。html
爲了支持擴展,Python API(應用程序員接口)定義了一組函數、宏和變量,它們提供對Python運行時系統大部分方面的訪問。Python API經過包含頭文件"Python.h"被合併到C源文件中。python
擴展模塊的編譯取決於其預期用途以及系統設置; 細節在後面的章節中給出。程序員
注意:C擴展接口是CPython特有的,擴展模塊不適用於其餘Python實現。在許多狀況下,能夠避免編寫C擴展並保持其餘實現的可移植性。例如,若是您的用例調用C庫函數或系統調用,則應考慮使用ctypes模塊或cffi庫,而不是編寫自定義C代碼。這些模塊容許您編寫Python代碼以與C代碼交互,而且在編寫和編譯C擴展模塊時,它們在Python的實現之間更具可移植性。shell
讓咱們建立一個名爲spam(Monty Python粉絲最喜歡的食物...)的擴展模塊,並假設咱們要爲C庫函數system()建立一個Python接口。該函數將一個以空字符結尾的字符串做爲參數,並返回一個整數。咱們但願這個函數能夠從Python中調用,以下所示:編程
>>> import spam >>> status = spam.system("ls -l")
首先建立一個文件spammodule.c(根據歷史經驗,若是一個模塊名稱爲spam,那麼包含其實現的C文件名稱爲spammodule.c;若是模塊名稱很長,好比spammify,那麼模塊名稱能夠是spammify.c)windows
咱們文件的第一行是:api
#include <Python.h>
它引入了Python API(若是你喜歡,你能夠添加一個描述模塊用途的註釋和一個版權聲明)。數組
注意:因爲Python可能會定義一些影響某些系統上標準頭文件的預處理器定義,所以必須在包含Python.h任何標準頭文件以前包含這些定義。緩存
由Python.h所定義的全部用戶可見符號都具備前綴Py或PY,除了在標準頭文件中定義的符號。爲了方便起見,"Python.h" 包括一些被Python解釋普遍使用的標準頭文件:<stdio.h>、<string.h>、 <errno.h>和<stdlib.h>
。若是是後者的頭文件沒有您的系統上存在,它將直接聲明malloc(),free()和 realloc()函數。安全
接下來咱們添加C語言函數到模塊文件中,在使用Python表達式spam.system(string)時被調用(咱們將很快看到它是如何被調用的):
static PyObject * spam_system(PyObject *self, PyObject *args) { const char *command; int sts; if (!PyArg_ParseTuple(args, "s", &command)) return NULL; sts = system(command); return PyLong_FromLong(sts); }
Python中的參數列表和C函數的參數列表之間有一個簡單的映射關係(例如單個表達式"ls -l")。C函數老是有兩個參數,一般命名爲self和args。
對於模塊級別的函數self變量指向模塊對象,對於對象方法self變量指向對象實例。
ARGS參數指向包含參數的Python元組對象。元組中的每一項都對應於調用參數列表中的參數。參數是Python對象 - 爲了在C函數中對它們作任何事情,咱們必須將它們轉換爲C值。Python API中的PyArg_ParseTuple()函數用於參數類型檢查並將它們轉換爲C值。它使用模板字符串來肯定參數的所需類型以及存儲轉換值的C變量的類型,稍後再詳細介紹。
若是全部參數都具備正確的類型而且被存儲到傳遞的變量地址中,則PyArg_ParseTuple()返回true(非零)。若是傳遞了一個無效參數列表,它將返回false(零)。在後一種狀況下,它也會引起適當的異常,因此調用函數能夠當即返回NULL(如咱們在示例中所見)。
整個Python解釋器中一個重要的約定以下:當一個函數失敗時,它應該設置一個異常條件並返回一個錯誤值(一般是一個NULL指針)。異常存儲在解釋器內的靜態全局變量中,若是此變量爲NULL,則不會發生異常。第二個全局變量存儲異常的"關聯值"(第二個參數raise)。第三個變量包含棧回溯,以防錯誤發生在Python代碼中。這三個變量是Python中sys.exc_info()執行結果的C等價物(請參閱sysPython庫參考中模塊部分)。瞭解他們瞭解錯誤是如何傳遞的很是重要。
Python API定義了許多函數來設置各類類型的異常。
最多見的是PyErr_SetString()。它的參數是一個異常對象和一個C字符串:異常對象一般是一個預約義的對象例如PyExc_ZeroDivisionError;C字符串指示錯誤的緣由,並轉換爲Python字符串對象存入異常的"關聯值"。
另外一個有用的函數是PyErr_SetFromErrno(),它只接受一個異常參數,並經過檢查全局變量來構造關聯的值errno。最通用的函數是PyErr_SetObject(),它接受兩個對象參數,異常及其相關的值。您不須要對傳遞給這些函數的對象執行Py_INCREF()。
經過調用PyErr_Occurred()您能夠非破壞性地測試是否設置了例外,它將返回當前的異常對象, 若是沒有發生異常,則返回NULL。一般您不須要調用PyErr_Occurred()以查看函數調用是否發生錯誤,由於您應該可以根據返回值進行分析。
當調用另外一個函數g的函數f檢測到後者失敗時,f自己應該返回一個錯誤值(一般爲NULL或-1)。它不該該再調用任何PyErr_*()
函數 - 由於g已經調用了。f的調用者應該也返回一個錯誤指示它的調用者,一樣不須要再調用任何PyErr_*()
函數,而後繼續 - 錯誤的最詳細緣由已經由首次檢測到它的函數報告(例如此處的g函數)。一旦錯誤到達Python解釋器的主循環,就會停止當前正在執行的Python代碼,並嘗試查找由Python程序員指定的異常處理程序。
(有些狀況下模塊實際上能夠經過調用另外一個PyErr_*()
函數來提供更詳細的錯誤消息,在這種狀況下能夠這樣作,但一般狀況下,這不是必需的,而且能夠致使有關緣由的信息的錯誤將會丟失:大多數操做可能因爲各類緣由而失敗。)
要忽略由失敗的函數調用設置的異常,必須經過調用PyErr_Clear()明確地清除異常狀況。C代碼調用PyErr_Clear()僅當它不想將錯誤傳遞給解釋器,但但願徹底由它本身處理(可能經過嘗試別的東西,或僞裝沒有出錯)。
每次失敗的malloc()調用都必須變成異常 - malloc()/realloc()的直接調用者必須本身調用PyErr_NoMemory()並返回失敗指示符。全部的對象建立函數(例如PyLong_FromLong())都已經這樣作了,因此這個註釋只與那些malloc()直接調用者有關。
還要注意的是,除了PyArg_ParseTuple()函數以外,返回整數狀態的函數一般返回正值或零值表示成功,-1表示失敗,如Unix系統調用。
最後,當你返回一個錯誤指示符時,要注意垃圾回收(經過向你已經建立的對象發出Py_XDECREF()或Py_DECREF()調用)!
選擇哪一個異常來徹底取決於你。有預先聲明的C對象與全部內置的Python異常相對應,好比 PyExc_ZeroDivisionError,您能夠直接使用它,但你應該明智地選擇異常 - 不要用PyExc_TypeError來表示文件沒法打開(應該可能PyExc_IOError)。若是參數列表有問題,PyArg_ParseTuple()函數一般會引起PyExc_TypeError異常。若是須要表示一個其值必須在一個特定的範圍或必須知足其餘條件的異常,PyExc_ValueError是適當的。
您也能夠爲模塊定義一個新的異常,一般您須要在文件的開頭聲明一個靜態對象變量:
static PyObject *SpamError;
並在模塊的初始化函數(PyInit_spam())中使用一個異常對象初始化它(如今先忽略錯誤檢查部分):
PyMODINIT_FUNC PyInit_spam(void) { PyObject *m; m = PyModule_Create(&spammodule); if (m == NULL) return NULL; SpamError = PyErr_NewException("spam.error", NULL, NULL); Py_INCREF(SpamError); PyModule_AddObject(m, "error", SpamError); return m; }
請注意,異常對象的Python名稱是spam.error。該 PyErr_NewException()函數能夠建立一個基類爲Exception的類(除非傳入另外一個類而不是NULL),如內置異常中所述。
還要注意,該SpamError變量保留對新建立的異常類的引用,這是故意的!因爲能夠經過外部代碼從模塊中刪除異常,因此須要擁有該類的引用,來確保SpamError不會由於被丟棄,從而成爲懸掛指針。若是它成爲懸掛指針,引起異常的C代碼可能會致使崩潰或其餘意外反作用。
稍後在本示例中咱們將討論PyMODINIT_FUNC
做爲函數返回類型的用法。
在你的擴展模塊中,能夠經過調用PyErr_SetString()來引起spam.error異常,以下所示:
static PyObject * spam_system(PyObject *self, PyObject *args) { const char *command; int sts; if (!PyArg_ParseTuple(args, "s", &command)) return NULL; sts = system(command); if (sts < 0) { PyErr_SetString(SpamError, "System command failed"); return NULL; } return PyLong_FromLong(sts); }
回到咱們的示例函數,您如今應該可以理解這個語句:
if (!PyArg_ParseTuple(args, "s", &command)) return NULL;
若是在參數列表中檢測到錯誤,則返回NULL(函數返回對象指針的錯誤指示符),依賴於PyArg_ParseTuple()設置的異常 。不然參數的字符串值已被複制到局部變量command。這是一個分配的指針,你不該該修改它指向的字符串(因此在標準C中,變量command應該被正確地聲明爲const char *command
)。
下一個語句是對Unix函數system()的調用,並傳遞給它咱們剛剛從PyArg_ParseTuple()獲得的字符串:
sts = system(command);
咱們的spam.system()函數必須將sts的值做爲Python對象返回,經過調用PyLong_FromLong()函數完成。
return PyLong_FromLong(sts);
在這種狀況下,它將返回一個整數對象。(是的,甚至整數都是Python中的堆對象!)
若是你有一個沒有返回值的C函數(函數返回值爲void),那麼相應的Python函數必須返回None。你須要這麼作(這是由Py_RETURN_NONE宏實現的):
Py_INCREF(Py_None); return Py_None;
Py_None是Python對象None的C名稱。正如咱們所看到的,它是一個真正的Python對象而不是NULL指針,這在大多數狀況下都意味着"錯誤"。
我承諾展現如何從Python程序中調用spam_system()。首先,咱們須要在"方法表"中列出其名稱和地址:
static PyMethodDef SpamMethods[] = { ... {"system", spam_system, METH_VARARGS, "Execute a shell command."}, ... {NULL, NULL, 0, NULL} /* Sentinel */ };
請注意第三項(METH_VARARGS)。這是一個標誌,告訴解釋器用於C函數的調用約定。它一般應該是METH_VARARGS
或METH_VARARGS | METH_KEYWORDS
。
僅使用METH_VARARGS
時,函數指望將Python級的參數做爲可以被PyArg_ParseTuple()解析的元組傳遞進來,關於這個功能的更多信息在下面提供。
若是關鍵字參數應該傳遞給函數,那麼METH_KEYWORDS
位將被設置。在這種狀況下,C函數應該接受第三個參數,該參數將成爲關鍵字字典,使用相似PyObject *PyArg_ParseTupleAndKeywords()
的函數來解析參數。
方法表必須在模塊定義結構中被引用:
static struct PyModuleDef spammodule = { PyModuleDef_HEAD_INIT, "spam", /* name of module */ spam_doc, /* module documentation, may be NULL */ -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */ SpamMethods };
這個結構又必須在模塊的初始化函數中傳遞給解釋器,初始化函數必須被命名PyInit_name(),其中name是模塊的名稱,而且應該是模塊文件中惟必定義的非static項目:
PyMODINIT_FUNC PyInit_spam(void) { return PyModule_Create(&spammodule); }
請注意,PyMODINIT_FUNC
聲明該函數的返回類型爲PyObject *
,聲明平臺所需的任何特殊連接聲明,而且聲明C++函數爲extern "C"
。
當Python程序第一次導入spam模塊時, PyInit_spam()被調用。它調用PyModule_Create()返回模塊對象,並根據PyMethodDef模塊定義中的表(結構數組)插入內置函數對象到新建立的模塊中。 PyModule_Create()返回一個指向它建立的模塊對象的指針。若是因致命錯誤而停止或者模塊初始化失敗,則返回NULL。init函數必須將模塊對象返回給其調用者,以便將其插入sys.modules
。
嵌入Python時,除非PyImport_Inittab表中有對應條目,不然不會自動調用PyInit_spam()函數。要將模塊添加到初始化表中,請使用PyImport_AppendInittab()(可選),而後導入模塊:
int main(int argc, char *argv[]) { wchar_t *program = Py_DecodeLocale(argv[0], NULL); if (program == NULL) { fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); exit(1); } /* Add a built-in module, before Py_Initialize */ PyImport_AppendInittab("spam", PyInit_spam); /* Pass argv[0] to the Python interpreter */ Py_SetProgramName(program); /* Initialize the Python interpreter. Required. */ Py_Initialize(); /* Optionally import the module; alternatively, import can be deferred until the embedded script imports it. */ PyImport_ImportModule("spam"); ... PyMem_RawFree(program); return 0; }
注意:從sys.modules刪除條目或將編譯的模塊導入進程中的多個解釋器(或者在fork()沒有插入的狀況下執行exec())會致使某些擴展模塊出現問題。擴展模塊做者在初始化內部數據結構時應該謹慎行事。
Python源代碼分發中包含了一個更實質性的示例模塊Modules/xxmodule.c。這個文件能夠做爲模板使用,或者只是做爲一個例子閱讀。
注意 與咱們的spam示例不一樣,xxmodule它使用多階段初始化 (Python 3.5中的新增功能),從PyInit_spam中返回PyModuleDef結構 ,而且將模塊建立留給導入機制。
在使用新擴展以前,還有兩件事要作:編譯並將其與Python系統連接。若是使用動態加載,細節可能取決於系統使用的動態加載樣式,查閱Building C and C++ Extensions以及Building C and C++ Extensions on Windows獲取更多信息。
若是你不能使用動態加載,或者若是你想讓你的模塊成爲Python解釋器的一個永久部分,你將不得不改變配置設置並重建解釋器。幸運的是,在Unix上這很是簡單:只需將您的文件(spammodule.c例如)放入Modules/解壓源代碼發行版的目錄中,而後在Modules/Setup.local
描述文件的文件中添加一行 :
spam spammodule.o
並經過在頂級目錄中運行make來重建解釋器。您也能夠在Modules/
子目錄中運行make,可是必須先經過運行make Makefile
來從新編譯Makefile。(每次更改Setup文件時都須要這樣作)
若是您的模塊須要額外的庫進行連接,這些庫也能夠在配置文件的行中列出,例如:
spam spammodule.o -lX11
到目前爲止,咱們已經集中在使C函數能夠從Python調用。反過來也頗有用:從C中調用Python函數。對於支持所謂的"回調"函數的庫尤爲如此。若是C接口使用回調函數,等效的Python一般須要爲Python程序員提供回調機制,該實現將須要從C回調調用Python回調函數。其餘用途也是能夠想象的。
幸運的是,Python解釋器很容易被遞歸調用,而且有一個標準的接口來調用Python函數。(我不會詳細討論如何使用特定的字符串做爲輸入來調用Python解析器 - 若是您有興趣,請查看Python源代碼中的-c命令行選項的實現Modules/main.c)
調用Python函數很容易。首先,Python程序必須以某種方式將Python函數對象傳遞給你。您應該提供一個功能(或其餘一些界面)來完成此操做。當這個函數被調用時,將一個指向Python函數對象的指針(注意Py_INCREF()它!)保存在全局變量中 - 或者任何你認爲合適的地方。例如,如下函數多是模塊定義的一部分:
static PyObject *my_callback = NULL; static PyObject * my_set_callback(PyObject *dummy, PyObject *args) { PyObject *result = NULL; PyObject *temp; if (PyArg_ParseTuple(args, "O:set_callback", &temp)) { if (!PyCallable_Check(temp)) { PyErr_SetString(PyExc_TypeError, "parameter must be callable"); return NULL; } Py_XINCREF(temp); /* Add a reference to new callback */ Py_XDECREF(my_callback); /* Dispose of previous callback */ my_callback = temp; /* Remember new callback */ /* Boilerplate to return "None" */ Py_INCREF(Py_None); result = Py_None; } return result; }
該功能必須使用METH_VARARGS
標誌向解釋器註冊,這在The Module’s Method Table and Initialization Function一節中有描述。PyArg_ParseTuple()函數及其參數說明記錄在Extracting Parameters in Extension Functions。
宏Py_XINCREF()和Py_XDECREF()用來增長/減小一個對象的引用計數,即便是NULL指針也是安全的(但請注意,在這種狀況下temp不會爲NULL)。有關它們的更多信息,請參閱參考計數部分。
稍後,當須要調用該函數的時候,您可使用C函數PyObject_CallObject()。這個函數有兩個參數,都是指向任意Python對象的指針:Python函數和參數列表。參數列表必須始終是一個元組對象,其長度是參數個數。調用沒有參數的Python函數時,傳入NULL或空元組; 調用一個參數的Python函數時,傳遞一個單例元組。當其格式字符串由零或更多格式代碼組成時,Py_BuildValue()返回一個元組。例如:
int arg; PyObject *arglist; PyObject *result; ... arg = 123; ... /* Time to call the callback */ arglist = Py_BuildValue("(i)", arg); result = PyObject_CallObject(my_callback, arglist); Py_DECREF(arglist);
PyObject_CallObject()返回一個Python對象指針:這是Python函數的返回值。在這個例子中,建立了一個新的元組做爲參數列表,該列表在調用PyObject_CallObject()後經過Py_DECREF調用被當即釋放。
返回值PyObject_CallObject()是"new":它是一個全新的對象或者它是引用計數已遞增的現有對象,因此除非你想把它保存在一個全局變量中,不然你應該以某種方式對獲得的結果執行Py_DECREF(),甚至(尤爲是!)若是你對它的價值不感興趣。
可是,在執行此操做以前,檢查返回值是否非空很是重要。若是爲空,則經過引起異常終止Python函數。若是是從Python調用的C代碼PyObject_CallObject(),應該向其Python調用者返回錯誤指示,以便解釋器能夠打印堆棧跟蹤或者調用能夠處理異常的Python代碼。若是這不可能或不可取,應經過調用清除異常PyErr_Clear()。例如:
if (result == NULL) return NULL; /* Pass error back */ ...use result... Py_DECREF(result);
根據Python回調函數接口,您可能還須要提供參數列表給PyObject_CallObject()。您可能須要構造一個新的元組做爲參數列表,最簡單的方法就是調用Py_BuildValue()。例如您想傳遞一個整型事件編碼,則可使用如下代碼:
PyObject *arglist; ... arglist = Py_BuildValue("(l)", eventcode); result = PyObject_CallObject(my_callback, arglist); Py_DECREF(arglist); if (result == NULL) return NULL; /* Pass error back */ /* Here maybe use the result */ Py_DECREF(result);
注意Py_DECREF(arglist)的位置,必須在調用以後,錯誤檢查以前!還要注意,嚴格來講這個代碼不完整:Py_BuildValue()可能會耗盡內存,須要進行檢查。
您也能夠經過使用支持參數和關鍵字參數的PyObject_Call()來調用函數。正如在上面的例子中,咱們Py_BuildValue()用來構造字典。
PyObject *dict; ... dict = Py_BuildValue("{s:i}", "name", val); result = PyObject_Call(my_callback, NULL, dict); Py_DECREF(dict); if (result == NULL) return NULL; /* Pass error back */ /* Here maybe use the result */ Py_DECREF(result);
PyArg_ParseTuple()函數聲明以下:
int PyArg_ParseTuple(PyObject *arg, const char *format, ...);
ARG參數必須是包含Python傳遞給C函數的參數列表的元組對象。format參數必須是一個格式字符串,其語法在Python/C API參考手冊Parsing arguments and building values中給出。其他的參數必須是變量的地址,其類型由格式字符串決定。
請注意,雖然PyArg_ParseTuple()檢查Python參數是否具備所需的類型,但它不能檢查傳遞給調用的C變量地址的有效性:若是在那裏犯錯,您的代碼可能會崩潰或至少覆蓋內存中的隨機位。因此要當心!
請注意,提供給調用者的任何Python對象引用都是借用引用,不要減小他們的參考計數!
一些示例調用:
#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ #include <Python.h> int ok; int i, j; long k, l; const char *s; Py_ssize_t size; ok = PyArg_ParseTuple(args, ""); /* No arguments */ /* Python call: f() */ ok = PyArg_ParseTuple(args, "s", &s); /* A string */ /* Possible Python call: f('whoops!') */ ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */ /* Possible Python call: f(1, 2, 'three') */ ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size); /* A pair of ints and a string, whose size is also returned */ /* Possible Python call: f((1, 2), 'three') */ { const char *file; const char *mode = "r"; int bufsize = 0; ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize); /* A string, and optionally another string and an integer */ /* Possible Python calls: f('spam') f('spam', 'w') f('spam', 'wb', 100000) */ } { int left, top, right, bottom, h, v; ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)", &left, &top, &right, &bottom, &h, &v); /* A rectangle and a point */ /* Possible Python call: f(((0, 0), (400, 300)), (10, 10)) */ } { Py_complex c; ok = PyArg_ParseTuple(args, "D:myfunction", &c); /* a complex, also providing a function name for errors */ /* Possible Python call: myfunction(1+2j) */ }
PyArg_ParseTupleAndKeywords()函數聲明以下:
int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict, const char *format, char *kwlist[], ...);
arg和format參數含義同PyArg_ParseTuple()函數,kwdict參數用來接收來自Python運行時的第三參數的關鍵字詞典,kwlist參數是以NULL結尾用於標識參數的字符串列表。成功時PyArg_ParseTupleAndKeywords()返回true,不然返回false,並引起異常。
注意:使用關鍵字參數時不能分析嵌套元組!傳入的關鍵字參數在kwlist不存在會致使TypeError異常。
下面是一個使用關鍵字的例子:
#include "Python.h" static PyObject * keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds) { int voltage; char *state = "a stiff"; char *action = "voom"; char *type = "Norwegian Blue"; static char *kwlist[] = {"voltage", "state", "action", "type", NULL}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist, &voltage, &state, &action, &type)) return NULL; printf("-- This parrot wouldn't %s if you put %i Volts through it.\n", action, voltage); printf("-- Lovely plumage, the %s -- It's %s!\n", type, state); Py_RETURN_NONE; } static PyMethodDef keywdarg_methods[] = { /* The cast of the function is necessary since PyCFunction values * only take two PyObject* parameters, and keywdarg_parrot() takes * three. */ {"parrot", (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS, "Print a lovely skit to standard output."}, {NULL, NULL, 0, NULL} /* sentinel */ }; static struct PyModuleDef keywdargmodule = { PyModuleDef_HEAD_INIT, "keywdarg", NULL, -1, keywdarg_methods }; PyMODINIT_FUNC PyInit_keywdarg(void) { return PyModule_Create(&keywdargmodule); }
函數PyArg_ParseTuple()聲明以下:
PyObject *Py_BuildValue(const char *format, ...);
它識別一組相似於被PyArg_ParseTuple()識別的format單元,但參數(輸入到函數而不是輸出)不能是指針,只能是值。它返回一個新的Python對象,適合從Python調用的C函數中返回。
與PyArg_ParseTuple()不一樣的是:後者要求其第一個參數是一個元組(由於Python參數列表老是在內部表示爲元組),Py_BuildValue()並不老是構建一個元組。它僅在格式字符串包含兩個或更多格式單元時才構建元組。若是格式字符串爲空,則返回None; 若是它只包含一個格式單元,則返回該格式單元描述的任何對象。要強制它返回一個大小爲0或1的元組,使用括號包裹format字符串。
示例(在左邊的調用,右邊的結果Python值):
Py_BuildValue("") None Py_BuildValue("i", 123) 123 Py_BuildValue("iii", 123, 456, 789) (123, 456, 789) Py_BuildValue("s", "hello") 'hello' Py_BuildValue("y", "hello") b'hello' Py_BuildValue("ss", "hello", "world") ('hello', 'world') Py_BuildValue("s#", "hello", 4) 'hell' Py_BuildValue("y#", "hello", 4) b'hell' Py_BuildValue("()") () Py_BuildValue("(i)", 123) (123,) Py_BuildValue("(ii)", 123, 456) (123, 456) Py_BuildValue("(i,i)", 123, 456) (123, 456) Py_BuildValue("[i,i]", 123, 456) [123, 456] Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456) {'abc': 123, 'def': 456} Py_BuildValue("((ii)(ii)) (ii)", 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
在C或C++等語言中,程序員負責動態分配和釋放堆上的內存,在C中使用函數malloc()和free(),在C++中使用new和delete。咱們將限制如下討論到C的狀況下。
每一個分配的內存塊malloc()最終都應該經過一次free()調用返還到可用內存池。在正確的時間調用free()很重要。若是一個塊的地址被遺忘,可是free()沒有被調用,它佔用的內存將不能被重用,直到程序終止,這被稱爲內存泄漏。另外一方面,若是一個程序繼續使用一個已經被調用過free()的內存塊,另外一個malloc()調用可能產生與該塊重用的衝突,這被稱爲使用釋放的內存。它與引用未初始化的數據有相同的不良後果 - 錯誤的結果,神祕的崩潰。
內存泄漏的常見緣由是代碼執行了一段的不尋常的路徑。例如,一個函數能夠分配一塊內存,作一些計算,而後釋放該塊。如今若是爲函數的計算添加一段錯誤檢測邏輯,而且可能會致使函數提早返回。在過早退出時忘記釋放分配的內存塊是很容易的,特別是當退出邏輯是後面添加到代碼中的時候。這種泄漏一旦被引入,每每不會被長時間檢測出來:錯誤退出只佔全部調用的一小部分,而大多數現代機器都有大量的虛擬內存,因此在長時間運行的過程當中頻繁調用問題函數纔會致使明顯的內存泄露。所以經過編碼慣例或策略來最大限度地減小這類錯誤,防止泄漏發生是很是重要的。
因爲Python大量使用malloc()和free(),它須要一種策略來避免內存泄漏以及釋放內存的使用。所選的方法稱爲參考計數。原理很簡單:每一個對象都包含一個計數器,當某個對象的引用存儲在某個地方時,該計數器會遞增,而當對該引用的引用被刪除時該計數器遞減。當計數器達到零時,對象的最後一個引用已被刪除,對象被釋放。
另外一種策略稱爲自動垃圾收集。(有時引用計數也被稱爲垃圾收集策略,所以我使用"自動"來區分二者)自動垃圾收集的一大優點是用戶無需顯式調用free() 。(另外一個聲稱的優勢是速度或內存使用方面的改進 - 但這並不難)。缺點是對於C,沒有真正的便攜式自動垃圾收集器,而引用計數能夠實現可移植性(只要函數malloc() 而且free()可用 - C標準保證)。也許有一天,一個足夠便攜的自動垃圾收集器將可用於C,在那以前咱們必須忍受引用計數。
當Python使用傳統的引用計數實現時,它還提供了一個循環檢測器,用於檢測循環引用。這容許應用程序不用擔憂建立直接或間接循環引用,而這是僅使用引用計數實現的垃圾收集的弱點。循環引用由包含(可能間接)引用自身的對象組成,所以循環中的每一個對象都有一個非零的引用計數。典型的引用計數實現不能回收任何處於循環引用中的對象內存或者引用,即便循環對象自己再也不有進一步的引用。
循環檢測器可以檢測垃圾循環並能夠回收它們。該gc模塊公開了一種運行探測器(collect()功能)的方法,以及配置接口和在運行時禁用探測器的功能。循環檢測器被視爲可選組件,雖然它是默認包含的,但它能夠在構建時使用Unix平臺(包括Mac OS X)上--without-cycle-gc的configure腳本選項來禁用,若是循環檢測器以這種方式被禁用,gc模塊將不可用。
宏Py_INCREF(x)和Py_DECREF(x)用來處理引用計數的遞增和遞減。Py_DECREF()當計數達到零時也釋放對象。爲了靈活性,它不直接調用free() - 而是經過調用類型對象中的函數指針,爲此(和其餘)每一個對象還包含一個指向其類型對象的指針。
如今最大的問題仍然是:什麼時候使用Py_INCREF(x)和Py_DECREF(x)?咱們先來介紹一些術語。沒有人"擁有"一個物體,可是您能夠擁有對象的引用。如今對象的引用計數定義爲擁有它的引用的數量。當再也不須要引用時,引用的全部者負責調用Py_DECREF()。引用的全部權能夠轉移。有三種方式能夠處理擁有的引用:傳遞它、存儲它或調用Py_DECREF(),忘記處理擁有的引用會形成內存泄漏。
也能夠借用對象的引用,引用的借用者不該該調用Py_DECREF(),引用的借用者不能比被借用者持有對象時間更長。被借用者釋放對象後,借用者繼續使用對象會有風險,應該徹底避免。
借用引用的優點在於,你不須要關心引用的處理,不會由於提早返回致使內存泄露,缺點就是可能存在使用已經釋放了的對象。
借用引用能夠經過調用Py_INCREF()變成引用擁有者,這不會影響借用出引用的擁有者的地位 - 它會建立一個新的擁有引用,並給予所有的擁有責任(新擁有者必須正確處理引用就像以前的擁有者那樣)。
不管什麼時候將對象引用傳入或傳出函數,它都是函數接口規範的一部分,而無論全部權是否與引用一塊兒傳輸。
大多數返回對象引用的函數都會將引用的全部權傳遞給它,特別是建立新對象的函數,例如PyLong_FromLong()和Py_BuildValue(),它們將返回對象的全部權交給接收方,即便該對象並非真正的新對象,您仍然會得到對該對象新引用的全部權,例如PyLong_FromLong()維護值的緩存並返回緩存的引用。
例如許多從其餘對象中提取對象的功能也會轉移引用的全部權,例如PyObject_GetAttrString()。然而有些例外:PyTuple_GetItem(),PyList_GetItem(),PyDict_GetItem()和 PyDict_GetItemString()返回的是元組、列表或字典的借用引用。
函數PyImport_AddModule()一樣返回一個借用引用,儘管實際上它可能真的建立了它返回的對象:這是可能的,由於對該對象的擁有引用存儲在sys.modules中。
當你將一個對象引用傳遞給另外一個函數時,一般這個函數會借用你的引用 - 若是它須要存儲它,調用Py_INCREF()來成爲一個獨立的全部者。這個規則有兩個重要的例外:PyTuple_SetItem()和 PyList_SetItem(),這些函數取代了傳遞給它們的物品的全部權 - 即便它們失敗了!(請注意PyDict_SetItem()不會接管全部權 - 他們是"正常"的)
當從Python調用C函數時,它會借用調用者的參數的引用。調用者擁有對該對象的引用,因此借用的引用的生命週期將獲得保證,直到該函數返回。只有當借用引用必須被存儲或傳遞時,必須經過調用Py_INCREF()轉化爲擁有引用。
從Python調用的C函數返回的對象引用必須是擁有引用 - 擁有權將從函數傳遞給調用者。
有幾種狀況看似無害地使用借用引用會致使問題,這些都與解釋器的隱式調用有關,這可能致使引用的全部者釋放它。
要了解的第一個也是最重要的案例是在不相關對象上使用Py_DECREF(),同時借用對列表項的引用。例如:
void bug(PyObject *list) { PyObject *item = PyList_GetItem(list, 0); PyList_SetItem(list, 1, PyLong_FromLong(0L)); PyObject_Print(item, stdout, 0); /* BUG! */ }
該函數首先借用一個引用list[0],而後用0替換list[1]的值,最後打印借用的引用。看起來沒什麼問題,對吧?但實際上不是!
讓咱們按照控制流程進入PyList_SetItem()。該列表擁有對其全部項目的引用,所以當項目1被替換時,它必須處理原始項目1。如今讓咱們假設原始項目1是用戶定義的類的一個實例,而且讓咱們進一步假設類定義了一種 del()方法。若是此類實例的引用計數爲1,則處置它將調用其__del__()方法。
因爲它是用Python編寫的,因此該__del__()方法能夠執行任意的Python代碼。它也執行了一些致使item引用無效的bug()函數?假設傳入bug()的列表能夠被__del__()方法訪問,它能夠執行一個del list[0]
語句,而且假定這是該對象的最後一個引用,它將釋放與它關聯的內存,從而致使item失效。
一旦知道問題的根源,就很容易想出解決方案:暫時增長引用計數。該函數的正確版本爲:
void no_bug(PyObject *list) { PyObject *item = PyList_GetItem(list, 0); Py_INCREF(item); PyList_SetItem(list, 1, PyLong_FromLong(0L)); PyObject_Print(item, stdout, 0); Py_DECREF(item); }
這是一個真實的故事。一個老版本的Python包含了這個bug的變種,有人花費了大量的時間在C調試器中弄清楚他的__del__()方法爲何會失敗......
借用引用的第二種狀況是涉及線程的變體。一般Python解釋器中的多個線程沒法相互獲取,由於有一個全局鎖定保護Python的整個對象空間。可是可使用宏臨時釋放此鎖 Py_BEGIN_ALLOW_THREADS,並使用Py_END_ALLOW_THREADS從新獲取它。這在阻塞I/O調用方面很常見,等待I/O完成時讓其餘線程使用處理器,顯然下面的函數與上一個函數具備相同的問題:
void bug(PyObject *list) { PyObject *item = PyList_GetItem(list, 0); Py_BEGIN_ALLOW_THREADS ...some blocking I/O call... Py_END_ALLOW_THREADS PyObject_Print(item, stdout, 0); /* BUG! */ }
通常來講,將對象引用做爲參數的函數並不指望你傳遞NULL指針給它們,而且若是你這樣作的話,將會致使崩潰。返回對象引用的函數一般僅當發生異常才返回NULL,不測試NULL參數的緣由是函數一般會將它們接收到的對象傳遞給其餘函數 - 若是每一個函數都要測試NULL,則會有大量冗餘測試,而且代碼運行速度會更慢。
當收到一個可能爲NULL的指針時,最好僅在最開始處測試NULL,例如在malloc()或可能引起異常的函數返回處測試。
宏Py_INCREF()和Py_DECREF()不檢查NULL指針-可是,它們的變體Py_XINCREF()和Py_XDECREF()會檢查。
用於檢查特定對象類型的宏(Pytype_Check())不會檢查NULL指針 - 由於有不少代碼在針對各類不一樣的預期類型時,會連續調用幾個宏來測試一個對象,若是檢查NULL指針的話會產生冗餘測試,因此對於檢查特定對象類型的宏沒有帶NULL檢查的變體。
C函數調用機制保證傳遞給C函數的參數列表(args在這個例子中)永遠不是NULL - 事實上它保證它老是一個元組。
讓一個NULL指針"逃到"Python用戶這是一個嚴重的錯誤。
能夠用C++編寫擴展模塊,但有些限制。若是主程序(Python解釋器)由C編譯器編譯和連接,則不能使用帶構造函數的全局對象或靜態對象。若是主程序由C++編譯器連接就沒有問題。Python解釋器調用的函數(特別是模塊初始化函數)必須使用extern "C"
聲明。沒有必要將Python頭文件包含在extern "C" {...}
- 若是定義了符號__cplusplus
,它們就會使用這種形式(全部最新的C++編譯器都定義了此符號)。
許多擴展模塊只是提供了Python中使用的新功能和類型,但有時擴展模塊中的代碼可能對其餘擴展模塊有用。例如擴展模塊能夠實現相似"收集"的類型,其工做方式相似於沒有順序的列表。就像標準的Python列表類型有一個容許擴展模塊建立和操做列表的C API同樣,這個新的集合類型應該有一組C函數用於直接從其餘擴展模塊進行操做。
乍一看,這看起來很簡單:只需編寫函數(沒有static聲明),提供適當的頭文件,並添加C API文檔。事實上若是全部的擴展模塊老是與Python解釋器靜態連接的話是沒問題的,可是當模塊用做共享庫時,一個模塊中定義的符號可能對另外一個模塊不可見,可見性的細節取決於操做系統,一些系統爲Python解釋器和全部擴展模塊(例如Windows)使用一個全局名稱空間,而其餘系統則須要在模塊連接時間(例如AIX)顯式導入導入的符號列表或者提供不一樣策略的選擇(大多數Unix系統),即便符號是全局可見的,其但願調用的函數的模塊可能還沒有加載!
所以可移植性不須要對符號可見性作出任何假設,這意味着除了模塊的初始化函數以外,擴展模塊中的全部符號都應聲明爲static,以免與其餘擴展模塊發生名稱衝突(如The Module’s Method Table and Initialization Function所述),這意味着從其餘擴展模塊訪問的符號必須以不一樣的方式導出。
Python提供了一種特殊的機制來將C級信息(指針)從一個擴展模塊傳遞到另外一個擴展模塊:Capsules。Capsule是一個存儲void *
指針的Python數據類型。Capsule只能經過C API建立和訪問,但它們能夠像任何其餘Python對象同樣傳遞。特別的是能夠分配擴展模塊名稱空間中的名稱給它們,而後其餘擴展模塊能夠導入該模塊,檢索該名稱的值,而後從Capsule檢索指針。
Capsules有許多方式導出擴展模塊的C API,每一個函數均可以得到本身的Capsule或者全部C API指針均可以存儲在一個地址在Capsule中發佈的數組中。存儲和檢索指針的各類任務能夠在提供代碼的模塊和客戶模塊之間以不一樣的方式分配。
不管您選擇哪一種方法,正確命名Capsules很是重要。函數PyCapsule_New()接受一個const char *
類型的名稱參數,您能夠傳入一個NULL,但咱們強烈建議您指定一個名稱。
特別是用於公開C API的Capsules應按照如下約定命名:
modulename.attributename
PyCapsule_Import()函數能夠很是方便的加載Capsule提供的C API,但前提是Capsule的名稱符合此慣例,這種行爲使得C API用戶能夠高度確定他們加載的Capsule包含正確的C API。
如下示例演示了一種將導出模塊的寫入程序的大部分負擔放在經常使用庫模塊的適當位置的方法。它將全部C API指針(示例中只有一個!)存儲在一個void指針數組中,該數組成爲Capsule的值。與模塊相對應的頭文件提供了一個宏,它負責導入模塊並檢索其C API指針,客戶端模塊只需在訪問C API以前調用此宏。
導出模塊是對A Simple Example模塊spam部分的修改。函數spam.system()
並不直接調用C庫函數system()
,而是PySpam_System()函數,現實中它固然會作一些更復雜的事情(好比爲每一個命令添加spam"), PySpam_System()函數也被導出到其餘擴展模塊。
函數PySpam_System()是一個簡單的C函數,像其餘同樣聲明爲static:
static int PySpam_System(const char *command) { return system(command); }
該函數spam_system()以一種簡單的方式進行修改:
static PyObject * spam_system(PyObject *self, PyObject *args) { const char *command; int sts; if (!PyArg_ParseTuple(args, "s", &command)) return NULL; sts = PySpam_System(command); return PyLong_FromLong(sts); }
在模塊的開頭,緊跟在行後面
#include "Python.h"
必須添加兩行:
#define SPAM_MODULE #include "spammodule.h"
#define
指明頭文件被包含在導出模塊,而不是客戶端模塊。最後模塊的初始化函數必須注意初始化C API指針數組:
PyMODINIT_FUNC PyInit_spam(void) { PyObject *m; static void *PySpam_API[PySpam_API_pointers]; PyObject *c_api_object; m = PyModule_Create(&spammodule); if (m == NULL) return NULL; /* Initialize the C API pointer array */ PySpam_API[PySpam_System_NUM] = (void *)PySpam_System; /* Create a Capsule containing the API pointer array's address */ c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL); if (c_api_object != NULL) PyModule_AddObject(m, "_C_API", c_api_object); return m; }
請注意PySpam_API聲明的是static,不然指針數組在PyInit_spam()終止時會消失!
大部分工做都在頭文件中spammodule.h,以下所示:
#ifndef Py_SPAMMODULE_H #define Py_SPAMMODULE_H #ifdef __cplusplus extern "C" { #endif /* Header file for spammodule */ /* C API functions */ #define PySpam_System_NUM 0 #define PySpam_System_RETURN int #define PySpam_System_PROTO (const char *command) /* Total number of C API pointers */ #define PySpam_API_pointers 1 #ifdef SPAM_MODULE /* This section is used when compiling spammodule.c */ static PySpam_System_RETURN PySpam_System PySpam_System_PROTO; #else /* This section is used in modules that use spammodule's API */ static void **PySpam_API; #define PySpam_System \ (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM]) /* Return -1 on error, 0 on success. * PyCapsule_Import will set an exception if there's an error. */ static int import_spam(void) { PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0); return (PySpam_API != NULL) ? 0 : -1; } #endif #ifdef __cplusplus } #endif #endif /* !defined(Py_SPAMMODULE_H) */
爲了訪問函數PySpam_System(),客戶端模塊必須在其初始化函數中調用函數(或者說宏)import_spam():
PyMODINIT_FUNC PyInit_client(void) { PyObject *m; m = PyModule_Create(&clientmodule); if (m == NULL) return NULL; if (import_spam() < 0) return NULL; /* additional initialization can happen here */ return m; }
這種方法的主要缺點是文件spammodule.h至關複雜,可是對於每一個導出的函數,其基本結構都是相同的,所以僅須要學習一次。