新聞中心
1. 使用 C 或 C++ 擴(kuò)展 python
如果你會用 C,添加新的 Python 內(nèi)置模塊會很簡單。以下兩件不能用 Python 直接做的事,可以通過 extension modules 來實現(xiàn):實現(xiàn)新的內(nèi)置對象類型;調(diào)用 C 的庫函數(shù)和系統(tǒng)調(diào)用。

10年積累的做網(wǎng)站、成都做網(wǎng)站經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先建設(shè)網(wǎng)站后付款的網(wǎng)站建設(shè)流程,更有澤州免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
為了支持?jǐn)U展,Python API(應(yīng)用程序編程接口)定義了一系列函數(shù)、宏和變量,可以訪問 Python 運(yùn)行時系統(tǒng)的大部分內(nèi)容。Python 的 API 可以通過在一個 C 源文件中引用 "Python.h" 頭文件來使用。
擴(kuò)展模塊的編寫方式取決與你的目的以及系統(tǒng)設(shè)置;下面章節(jié)會詳細(xì)介紹。
備注
C擴(kuò)展接口特指CPython,擴(kuò)展模塊無法在其他Python實現(xiàn)上工作。在大多數(shù)情況下,應(yīng)該避免寫C擴(kuò)展,來保持可移植性。舉個例子,如果你的用例調(diào)用了C庫或系統(tǒng)調(diào)用,你應(yīng)該考慮使用 ctypes 模塊或 cffi 庫,而不是自己寫C代碼。這些模塊允許你寫Python代碼來接口C代碼,而且可移植性更好。不知為何編譯失敗了。
1.1. 一個簡單的例子
讓我們創(chuàng)建一個擴(kuò)展模塊 spam (Monty Python 粉絲最喜歡的食物…) 并且想要創(chuàng)建對應(yīng) C 庫函數(shù) system() 1 的 Python 接口。 這個函數(shù)接受一個以 null 結(jié)尾的字符串參數(shù)并返回一個整數(shù)。 我們希望可以在 Python 中以如下方式調(diào)用此函數(shù):
>>> import spam>>> status = spam.system("ls -l")
首先創(chuàng)建一個 spammodule.c 文件。(傳統(tǒng)上,如果一個模塊叫 spam,則對應(yīng)實現(xiàn)它的 C 文件叫 spammodule.c;如果這個模塊名字非常長,比如 spammify,則這個模塊的文件可以直接叫 spammify.c。)
文件中開始的兩行是:
#define PY_SSIZE_T_CLEAN#include
這會導(dǎo)入 Python API(如果你喜歡,你可以在這里添加描述模塊目標(biāo)和版權(quán)信息的注釋)。
備注
由于 Python 可能會定義一些能在某些系統(tǒng)上影響標(biāo)準(zhǔn)頭文件的預(yù)處理器定義,因此在包含任何標(biāo)準(zhǔn)頭文件之前,你 必須 先包含 Python.h。
推薦總是在 Python.h 前定義 PY_SSIZE_T_CLEAN 。查看 提取擴(kuò)展函數(shù)的參數(shù) 來了解這個宏的更多內(nèi)容。
所有在 Python.h 中定義的用戶可見的符號都具有 Py 或 PY 前綴,已在標(biāo)準(zhǔn)頭文件中定義的那些除外。 考慮到便利性,也由于其在 Python 解釋器中被廣泛使用,"Python.h" 還包含了一些標(biāo)準(zhǔn)頭文件: ,, 和 。 如果后面的頭文件在你的系統(tǒng)上不存在,它還會直接聲明函數(shù) malloc(),free() 和 realloc()。
下面添加C函數(shù)到擴(kuò)展模塊,當(dāng)調(diào)用 spam.system(string) 時會做出響應(yīng),(我們稍后會看到調(diào)用):
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);}
有個直接翻譯參數(shù)列表的方法(舉個例子,單獨(dú)的 "ls -l" )到要傳遞給C函數(shù)的參數(shù)。C函數(shù)總是有兩個參數(shù),通常名字是 self 和 args 。
對模塊級函數(shù), self 參數(shù)指向模塊對象;對于方法則指向?qū)ο髮嵗?/p>
args 參數(shù)是指向一個 Python 的 tuple 對象的指針,其中包含參數(shù)。 每個 tuple 項對應(yīng)一個調(diào)用參數(shù)。 這些參數(shù)也全都是 Python 對象 —- 要在我們的 C 函數(shù)中使用它們就需要先將其轉(zhuǎn)換為 C 值。 Python API 中的函數(shù) PyArg_ParseTuple() 會檢查參數(shù)類型并將其轉(zhuǎn)換為 C 值。 它使用模板字符串確定需要的參數(shù)類型以及存儲被轉(zhuǎn)換的值的 C 變量類型。 細(xì)節(jié)將稍后說明。
PyArg_ParseTuple() 在所有參數(shù)都有正確類型且組成部分按順序放在傳遞進(jìn)來的地址里時,返回真(非零)。其在傳入無效參數(shù)時返回假(零)。在后續(xù)例子里,還會拋出特定異常,使得調(diào)用的函數(shù)可以理解返回 NULL (也就是例子里所見)。
1.2. 關(guān)于錯誤和異常
整個 Python 解釋器系統(tǒng)有一個如下所述的重要慣例:當(dāng)一個函數(shù)運(yùn)行失敗時,它應(yīng)當(dāng)設(shè)置一個異常條件并返回一個錯誤值(通常為 -1 或 NULL 指針)。 異常信息保存在解釋器線程狀態(tài)的三個成員中。 如果沒有異常則它們的值為 NULL。 在其他情況下它們是 sys.exc_info() 所返回的 Python 元組的成員的 C 對應(yīng)物。 它們分別是異常類型、異常實例和回溯對象。 理解它們對于理解錯誤是如何被傳遞的非常重要。
Python API中定義了一些函數(shù)來設(shè)置這些變量。
最常用的就是 PyErr_SetString()。 其參數(shù)是異常對象和 C 字符串。 異常對象一般是像 PyExc_ZeroDivisionError 這樣的預(yù)定義對象。 C 字符串指明異常原因,并被轉(zhuǎn)換為一個 Python 字符串對象存儲為異常的“關(guān)聯(lián)值”。
另一個有用的函數(shù)是 PyErr_SetFromErrno() ,僅接受一個異常對象,異常描述包含在全局變量 errno 中。最通用的函數(shù)還是 PyErr_SetObject() ,包含兩個參數(shù),分別為異常對象和異常描述。你不需要使用 Py_INCREF() 來增加傳遞到其他函數(shù)的參數(shù)對象的引用計數(shù)。
你可以通過 PyErr_Occurred() 在不造成破壞的情況下檢測是否設(shè)置了異常。 這將返回當(dāng)前異常對象,或者如果未發(fā)生異常則返回 NULL。 你通常不需要調(diào)用 PyErr_Occurred() 來查看函數(shù)調(diào)用中是否發(fā)生了錯誤,因為你應(yīng)該能從返回值中看出來。
When a function f that calls another function g detects that the latter fails, f should itself return an error value (usually NULL or -1). It should not call one of the PyErr_* functions —- one has already been called by g. f‘s caller is then supposed to also return an error indication to its caller, again without calling PyErr_*, and so on —- the most detailed cause of the error was already reported by the function that first detected it. Once the error reaches the Python interpreter’s main loop, this aborts the currently executing Python code and tries to find an exception handler specified by the Python programmer.
(There are situations where a module can actually give a more detailed error message by calling another PyErr_* function, and in such cases it is fine to do so. As a general rule, however, this is not necessary, and can cause information about the cause of the error to be lost: most operations can fail for a variety of reasons.)
想要忽略由一個失敗的函數(shù)調(diào)用所設(shè)置的異常,異常條件必須通過調(diào)用 PyErr_Clear() 顯式地被清除。 C 代碼應(yīng)當(dāng)調(diào)用 PyErr_Clear() 的唯一情況是如果它不想將錯誤傳給解釋器而是想完全由自己來處理它(可能是嘗試其他方法,或是假裝沒有出錯)。
每次失敗的 malloc() 調(diào)用必須轉(zhuǎn)換為一個異常。 malloc() (或 realloc() )的直接調(diào)用者必須調(diào)用 PyErr_NoMemory() 來返回錯誤來提示。所有對象創(chuàng)建函數(shù)(例如 PyLong_FromLong() )已經(jīng)這么做了,所以這個提示僅用于直接調(diào)用 malloc() 的情況。
還要注意的是,除了 PyArg_ParseTuple() 等重要的例外,返回整數(shù)狀態(tài)碼的函數(shù)通常都是返回正值或零來表示成功,而以 -1 表示失敗,如同 Unix 系統(tǒng)調(diào)用一樣。
最后,當(dāng)你返回一個錯誤指示器時要注意清理垃圾(通過為你已經(jīng)創(chuàng)建的對象執(zhí)行 Py_XDECREF() 或 Py_DECREF() 調(diào)用)!
選擇引發(fā)哪個異常完全取決于你的喜好。 所有內(nèi)置的 Python 異常都有對應(yīng)的預(yù)聲明 C 對象,例如 PyExc_ZeroDivisionError,你可以直接使用它們。 當(dāng)然,你應(yīng)當(dāng)明智地選擇異常 —- 不要使用 PyExc_TypeError 來表示一個文件無法被打開 (那大概應(yīng)該用 PyExc_IOError)。 如果參數(shù)列表有問題,PyArg_ParseTuple() 函數(shù)通常會引發(fā) PyExc_TypeError。 如果你想要一個參數(shù)的值必須處于特定范圍之內(nèi)或必須滿足其他條件,則適宜使用 PyExc_ValueError。
你也可以為你的模塊定義一個唯一的新異常。需要在文件前部聲明一個靜態(tài)對象變量,如:
static PyObject *SpamError;
并且在你的模塊的初始化函數(shù) (PyInit_spam()) 中使用一個異常對象來初始化:
PyMODINIT_FUNCPyInit_spam(void){PyObject *m;m = PyModule_Create(&spammodule);if (m == NULL)return NULL;SpamError = PyErr_NewException("spam.error", NULL, NULL);Py_XINCREF(SpamError);if (PyModule_AddObject(m, "error", SpamError) < 0) {Py_XDECREF(SpamError);Py_CLEAR(SpamError);Py_DECREF(m);return NULL;}return m;}
注意異常對象的Python名字是 spam.error 。而 PyErr_NewException() 函數(shù)可以創(chuàng)建一個類,其基類為 Exception (除非是另一個類傳入以替換 NULL ), 細(xì)節(jié)參見 內(nèi)置異常 。
同樣注意的是創(chuàng)建類保存了 SpamError 的一個引用,這是有意的。為了防止被垃圾回收掉,否則 SpamError 隨時會成為野指針。
一會討論 PyMODINIT_FUNC 作為函數(shù)返回類型的用法。
spam.error 異??梢栽跀U(kuò)展模塊中拋出,通過 PyErr_SetString() 函數(shù)調(diào)用,如下:
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);}
1.3. 回到例子
回到前面的例子,你應(yīng)該明白下面的代碼:
if (!PyArg_ParseTuple(args, "s", &command))return NULL;
如果在參數(shù)列表中檢測到錯誤,它將返回 NULL (該值是返回對象指針的函數(shù)所使用的錯誤提示),這取決于 PyArg_ParseTuple() 設(shè)置的異常。 在其他情況下參數(shù)的字符串值會被拷貝到局部變量 command。 這是一個指針賦值并且你不應(yīng)該修改它所指向的字符串 (因此在標(biāo)準(zhǔn) C 中,變量 command 應(yīng)當(dāng)被正確地聲明為 const char *command)。
下一個語句使用UNIX系統(tǒng)函數(shù) system() ,傳遞給他的參數(shù)是剛才從 PyArg_ParseTuple() 取出的:
sts = system(command);
我們的 spam.system() 函數(shù)必須返回 sts 的值作為Python對象。這通過使用函數(shù) PyLong_FromLong() 來實現(xiàn)。
return PyLong_FromLong(sts);
在這種情況下,會返回一個整數(shù)對象,(這個對象會在Python堆里面管理)。
If you have a C function that returns no useful argument (a function returning void), the corresponding Python function must return None. You need this idiom to do so (which is implemented by the Py_RETURN_NONE macro):
Py_INCREF(Py_None);return Py_None;
Py_None 是特殊 Python 對象 None 所對應(yīng)的 C 名稱。 它是一個真正的 Python 對象而不是 NULL 指針,如我們所見,后者在大多數(shù)上下文中都意味著“錯誤”。
1.4. 模塊方法表和初始化函數(shù)
為了展示 spam_system() 如何被Python程序調(diào)用。把函數(shù)聲明為可以被Python調(diào)用,需要先定義一個方法表 “method table” 。
static PyMethodDef SpamMethods[] = {...{"system", spam_system, METH_VARARGS,"Execute a shell command."},...{NULL, NULL, 0, NULL} /* Sentinel */};
注意第三個參數(shù) ( METH_VARARGS ) ,這個標(biāo)志指定會使用C的調(diào)用慣例。可選值有 METH_VARARGS 、 METH_VARARGS | METH_KEYWORDS 。值 0 代表使用 PyArg_ParseTuple() 的陳舊變量。
如果單獨(dú)使用 METH_VARARGS ,函數(shù)會等待Python傳來tuple格式的參數(shù),并最終使用 PyArg_ParseTuple() 進(jìn)行解析。
METH_KEYWORDS 值表示接受關(guān)鍵字參數(shù)。這種情況下C函數(shù)需要接受第三個 PyObject * 對象,表示字典參數(shù),使用 PyArg_ParseTupleAndKeywords() 來解析出參數(shù)。
這個方法表必須被模塊定義結(jié)構(gòu)所引用。
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};
這個結(jié)構(gòu)體必須傳遞給解釋器的模塊初始化函數(shù)。初始化函數(shù)必須命名為 PyInit_name() ,其中 name 是模塊的名字,并應(yīng)該定義為非 static ,且在模塊文件里:
PyMODINIT_FUNCPyInit_spam(void){return PyModule_Create(&spammodule);}
注意 PyMODINIT_FUNC 將函數(shù)聲明為 PyObject * 返回類型,聲明了任何平臺所要求的特殊鏈接聲明,并針對 C++ 將函數(shù)聲明為 extern "C"。
當(dāng) Python 程序首次導(dǎo)入 spam 模塊時, PyInit_spam() 會被調(diào)用。 (有關(guān)嵌入 Python 的注釋參見下文。) 它將調(diào)用 PyModule_Create(),該函數(shù)會返回一個模塊對象,并基于在模塊定義中找到的表將內(nèi)置函數(shù)對象插入到新創(chuàng)建的模塊中(該表是一個 PyMethodDef 結(jié)構(gòu)體的數(shù)組)。 PyModule_Create() 返回一個指向它所創(chuàng)建的模塊對象的指針。 它可能會因程度嚴(yán)重的特定錯誤而中止,或者在模塊無法成功初始化時返回 NULL。 初始化函數(shù)必須返回模塊對象給其調(diào)用者,這樣它就可以被插入到 sys.modules 中。
當(dāng)嵌入Python時, PyInit_spam() 函數(shù)不會被自動調(diào)用,除非放在 PyImport_Inittab 表里。要添加模塊到初始化表,使用 PyImport_AppendInittab() ,可選的跟著一個模塊的導(dǎo)入。
intmain(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 */if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {fprintf(stderr, "Error: could not extend in-built modules table\n");exit(1);}/* Pass argv[0] to the Python interpreter */Py_SetProgramName(program);/* Initialize the Python interpreter. Required.If this step fails, it will be a fatal error. */Py_Initialize();/* Optionally import the module; alternatively,import can be deferred until the embedded scriptimports it. */PyObject *pmodule = PyImport_ImportModule("spam");if (!pmodule) {PyErr_Print();fprintf(stderr, "Error: could not import module 'spam'\n");}...PyMem_RawFree(program);return 0;}
備注
要從 sys.modules 刪除實體或?qū)胍丫幾g模塊到一個進(jìn)程里的多個解釋器(或使用 fork() 而沒用 exec() )會在一些擴(kuò)展模塊上產(chǎn)生錯誤。擴(kuò)展模塊作者可以在初始化內(nèi)部數(shù)據(jù)結(jié)構(gòu)時給出警告。
更多關(guān)于模塊的現(xiàn)實的例子包含在Python源碼包的 Modules/xxmodule.c 中。這些文件可以用作你的代碼模板,或者學(xué)習(xí)。腳本 modulator.py 包含在源碼發(fā)行版或Windows安裝中,提供了一個簡單的GUI,用來聲明需要實現(xiàn)的函數(shù)和對象,并且可以生成供填入的模板。腳本在 Tools/modulator/ 目錄。查看README以了解用法。
備注
不像我們的 spam 例子, xxmodule 使用了 多階段初始化 (Python3.5開始引入), PyInit_spam 會返回一個 PyModuleDef 結(jié)構(gòu)體,然后創(chuàng)建的模塊放到導(dǎo)入機(jī)制。細(xì)節(jié)參考 PEP 489 的多階段初始化。
1.5. 編譯和鏈接
在你能使用你的新寫的擴(kuò)展之前,你還需要做兩件事情:使用 Python 系統(tǒng)來編譯和鏈接。如果你使用動態(tài)加載,這取決于你使用的操作系統(tǒng)的動態(tài)加載機(jī)制;更多信息請參考編譯擴(kuò)展模塊的章節(jié)( 構(gòu)建C/C++擴(kuò)展 章節(jié)),以及在 Windows 上編譯需要的額外信息( 在 Windows 上構(gòu)建 C 和 C++ 擴(kuò)展 章節(jié))。
如果你不使用動態(tài)加載,或者想要讓模塊永久性的作為Python解釋器的一部分,就必須修改配置設(shè)置,并重新構(gòu)建解釋器。幸運(yùn)的是在Unix上很簡單,只需要把你的文件 ( spammodule.c 為例) 放在解壓縮源碼發(fā)行包的 Modules/ 目錄下,添加一行到 Modules/Setup.local 來描述你的文件:
spam spammodule.o
然后在頂層目錄運(yùn)行 make 來重新構(gòu)建解釋器。你也可以在 Modules/ 子目錄使用 make,但是你必須先重建 Makefile 文件,然后運(yùn)行 ‘make Makefile’ 命令。(你每次修改 Setup 文件都需要這樣操作。)
如果你的模塊需要額外的鏈接,這些內(nèi)容可以列出在配置文件里,舉個實例:
spam spammodule.o -lX11
1.6. 在C中調(diào)用Python函數(shù)
迄今為止,我們一直把注意力集中于讓Python調(diào)用C函數(shù),其實反過來也很有用,就是用C調(diào)用Python函數(shù)。這在回調(diào)函數(shù)中尤其有用。如果一個C接口使用回調(diào),那么就要實現(xiàn)這個回調(diào)機(jī)制。
幸運(yùn)的是,Python解釋器是比較方便回調(diào)的,并給標(biāo)準(zhǔn)Python函數(shù)提供了標(biāo)準(zhǔn)接口。(這里就不再詳述解析Python字符串作為輸入的方式,如果有興趣可以參考 Python/pythonmain.c 中的 -c 命令行代碼。)
調(diào)用Python函數(shù)很簡單,首先Python程序要傳遞Python函數(shù)對象。應(yīng)該提供個函數(shù)(或其他接口)來實現(xiàn)。當(dāng)調(diào)用這個函數(shù)時,用全局變量保存Python函數(shù)對象的指針,還要調(diào)用 (Py_INCREF()) 來增加引用計數(shù),當(dāng)然不用全局變量也沒什么關(guān)系。舉個例子,如下函數(shù)可能是模塊定義的一部分:
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;}
這個函數(shù)必須使用 METH_VARARGS 標(biāo)志注冊到解釋器,這在 模塊方法表和初始化函數(shù) 章節(jié)會描述。 PyArg_ParseTuple() 函數(shù)及其參數(shù)的文檔在 提取擴(kuò)展函數(shù)的參數(shù) 。
Py_XINCREF() 和 Py_XDECREF() 這兩個宏可增加/減少一個對象的引用計數(shù),并且當(dāng)存在 NULL 指針時仍可保證安全 (但請注意在這個上下文中 temp 將不為 NULL)。 更多相關(guān)信息請參考 引用計數(shù) 章節(jié)。
隨后,當(dāng)要調(diào)用此函數(shù)時,你將調(diào)用 C 函數(shù) PyObject_CallObject()。 該函數(shù)有兩個參數(shù),它們都屬于指針,指向任意 Python 對象:即 Python 函數(shù),及其參數(shù)列表。 參數(shù)列表必須總是一個元組對象,其長度即參數(shù)的個數(shù)量。 要不帶參數(shù)地調(diào)用 Python 函數(shù),則傳入 NULL 或一個空元組;要帶一個參數(shù)調(diào)用它,則傳入一個單元組。 Py_BuildValue() 會在其格式字符串包含一對圓括號內(nèi)的零個或多個格式代碼時返回一個元組。 例如:
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函數(shù)的返回值。 PyObject_CallObject() 是一個對其參數(shù) “引用計數(shù)無關(guān)” 的函數(shù)。例子中新的元組創(chuàng)建用于參數(shù)列表,并且在 PyObject_CallObject() 之后立即使用了 Py_DECREF() 。
PyObject_CallObject() 的返回值總是“新”的:要么是一個新建的對象;要么是已有對象,但增加了引用計數(shù)。所以除非你想把結(jié)果保存在全局變量中,你需要對這個值使用 Py_DECREF(),即使你對里面的內(nèi)容(特別?。┎桓信d趣。
但是在你這么做之前,很重要的一點是檢查返回值不是 NULL。 如果是的話,Python 函數(shù)會終止并引發(fā)異常。 如果調(diào)用 PyObject_CallObject() 的 C 代碼是在 Python 中發(fā)起調(diào)用的,它應(yīng)當(dāng)立即返回一個錯誤來告知其 Python 調(diào)用者,以便解釋器能打印棧回溯信息,或者讓調(diào)用方 Python 代碼能處理該異常。 如果這無法做到或不合本意,則應(yīng)當(dāng)通過調(diào)用 PyErr_Clear() 來清除異常。 例如:
if (result == NULL)return NULL; /* Pass error back */...use result...Py_DECREF(result);
依賴于具體的回調(diào)函數(shù),你還要提供一個參數(shù)列表到 PyObject_CallObject() 。在某些情況下參數(shù)列表是由Python程序提供的,通過接口再傳到回調(diào)函數(shù)對象。這樣就可以不改變形式直接傳遞。另外一些時候你要構(gòu)造一個新的元組來傳遞參數(shù)。最簡單的方法就是 Py_BuildValue() 函數(shù)構(gòu)造tuple。舉個例子,你要傳遞一個事件代碼時可以用如下代碼:
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) 所在處會立即調(diào)用,在錯誤檢查之前。當(dāng)然還要注意一些常規(guī)的錯誤,比如 Py_BuildValue() 可能會遭遇內(nèi)存不足等等。
當(dāng)你調(diào)用函數(shù)時還需要注意,用關(guān)鍵字參數(shù)調(diào)用 PyObject_Call() ,需要支持普通參數(shù)和關(guān)鍵字參數(shù)。有如如上例子中,我們使用 Py_BuildValue() 來構(gòu)造字典。
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);
1.7. 提取擴(kuò)展函數(shù)的參數(shù)
函數(shù) PyArg_ParseTuple() 的聲明如下:
int PyArg_ParseTuple(PyObject *arg, const char *format, ...);
參數(shù) arg 必須是一個元組對象,包含從 Python 傳遞給 C 函數(shù)的參數(shù)列表。format 參數(shù)必須是一個格式字符串,語法請參考 Python C/API 手冊中的 解析參數(shù)并構(gòu)建值變量。剩余參數(shù)是各個變量的地址,類型要與格式字符串對應(yīng)。
注意 PyArg_ParseTuple() 會檢測他需要的Python參數(shù)類型,卻無法檢測傳遞給他的C變量地址,如果這里出錯了,可能會在內(nèi)存中隨機(jī)寫入東西,小心。
注意任何由調(diào)用者提供的 Python 對象引用是 借來的 引用;不要遞減它們的引用計數(shù)!
一些調(diào)用的例子:
#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */#include
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) */}
1.8. 給擴(kuò)展函數(shù)的關(guān)鍵字參數(shù)
函數(shù) PyArg_ParseTupleAndKeywords() 聲明如下:
int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,const char *format, char *kwlist[], ...);
arg 與 format 形參與 PyArg_ParseTuple() 函數(shù)所定義的一致。 kwdict 形參是作為第三個參數(shù)從 Python 運(yùn)行時接收的關(guān)鍵字字典。 kwlist 形參是以 NULL 結(jié)尾的字符串列表,它被用來標(biāo)識形參;名稱從左至右與來自 format 的類型信息相匹配。 如果執(zhí)行成功,PyArg_ParseTupleAndKeywords() 會返回真值,否則返回假值并引發(fā)一個適當(dāng)?shù)漠惓!?/p>
備注
嵌套的元組在使用關(guān)鍵字參數(shù)時無法生效,不在 kwlist 中的關(guān)鍵字參數(shù)會導(dǎo)致 TypeError 異常。
如下例子是使用關(guān)鍵字參數(shù)的例子模塊,作者是 Geoff Philbrick ([email protected]):
#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */#includestatic PyObject *keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds){int voltage;const char *state = "a stiff";const char *action = "voom";const 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)(void(*)(void))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_FUNCPyInit_keywdarg(void){return PyModule_Create(&keywdargmodule);}
1.9. 構(gòu)造任意值
這個函數(shù)與 PyArg_ParseTuple() 很相似,聲明如下:
PyObject *Py_BuildValue(const char *format, ...);
接受一個格式字符串,與 PyArg_ParseTuple() 相同,但是參數(shù)必須是原變量的地址指針(輸入給函數(shù),而非輸出)。最終返回一個Python對象適合于返回C函數(shù)調(diào)用給Python代碼。
一個與 PyArg_ParseTuple() 的不同是,后面可能需要的要求返回一個元組(Python參數(shù)里誒包總是在內(nèi)部描述為元組),比如用于傳遞給其他Python函數(shù)以參數(shù)。 Py_BuildValue() 并不總是生成元組,在多于1個格式字符串時會生成元組,而如果格式字符串為空則返回 None ,一個參數(shù)則直接返回該參數(shù)的對象。如果要求強(qiáng)制生成一個長度為0的元組,或包含一個元素的元組,需要在格式字符串中加上括號。
例子(左側(cè)是調(diào)用,右側(cè)是Python值結(jié)果):
Py_BuildValue("") NonePy_BuildValue("i", 123) 123Py_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))
1.10. 引用計數(shù)
在C/C++語言中,程序員負(fù)責(zé)動態(tài)分配和回收堆heap當(dāng)中的內(nèi)存。在C里,通過函數(shù) malloc() 和 free() 來完成。在C++里是操作 new 和 delete 來實現(xiàn)相同的功能。
每個由 malloc() 分配的內(nèi)存塊,最終都要由 free() 退回到可用內(nèi)存池里面去。而調(diào)用 free() 的時機(jī)非常重要,如果一個內(nèi)存塊忘了 free() 則會導(dǎo)致內(nèi)存泄漏,這塊內(nèi)存在程序結(jié)束前將無法重新使用。這叫做 內(nèi)存泄漏 。而如果對同一內(nèi)存塊 free() 了以后,另外一個指針再次訪問,則再次使用 malloc() 復(fù)用這塊內(nèi)存會導(dǎo)致沖突。這叫做 野指針 。等同于使用未初始化的數(shù)據(jù),core dump,錯誤結(jié)果,神秘的崩潰等。
內(nèi)存泄露往往發(fā)生在一些并不常見的代碼流程上面。比如一個函數(shù)申請了內(nèi)存以后,做了些計算,然后釋放內(nèi)存塊?,F(xiàn)在一些對函數(shù)的修改可能增加對計算的測試并檢測錯誤條件,然后過早的從函數(shù)返回了。這很容易忘記在退出前釋放內(nèi)存,特別是后期修改的代碼。這種內(nèi)存泄漏,一旦引入,通常很長時間都難以檢測到,錯誤退出被調(diào)用的頻度較低,而現(xiàn)代電腦又有非常巨大的虛擬內(nèi)存,所以泄漏僅在長期運(yùn)行或頻繁調(diào)用泄漏函數(shù)時才會變得明顯。因此,有必要避免內(nèi)存泄漏,通過代碼規(guī)范會策略來最小化此類錯誤。
Python通過 malloc() 和 free() 包含大量的內(nèi)存分配和釋放,同樣需要避免內(nèi)存泄漏和野指針。他選擇的方法就是 引用計數(shù) 。其原理比較簡單:每個對象都包含一個計數(shù)器,計數(shù)器的增減與對象引用的增減直接相關(guān),當(dāng)引用計數(shù)為0時,表示對象已經(jīng)沒有存在的意義了,對象就可以刪除了。
另一個叫法是 自動垃圾回收 。(有時引用計數(shù)也被看作是垃圾回收策略,于是這里的”自動”用以區(qū)分兩者)。自動垃圾回收的優(yōu)點是用戶不需要明確的調(diào)用 free() 。(另一個優(yōu)點是改善速度或內(nèi)存使用,然而這并不難)。缺點是對C,沒有可移植的自動垃圾回收器,而引用計數(shù)則可以可移植的實現(xiàn)(只要 malloc() 和 free() 函數(shù)是可用的,這也是C標(biāo)準(zhǔn)擔(dān)保的)。也許以后有一天會出現(xiàn)可移植的自動垃圾回收器,但在此前我們必須與引用計數(shù)一起工作。
Python使用傳統(tǒng)的引用計數(shù)實現(xiàn),也提供了循環(huán)監(jiān)測器,用以檢測引用循環(huán)。這使得應(yīng)用無需擔(dān)心直接或間接的創(chuàng)建了循環(huán)引用,這是引用計數(shù)垃圾收集的一個弱點。引用循環(huán)是對象(可能直接)的引用了本身,所以循環(huán)中的每個對象的引用計數(shù)都不是0。典型的引用計數(shù)實現(xiàn)無法回收處于引用循環(huán)中的對象,或者被循環(huán)所引用的對象,哪怕沒有循環(huán)以外的引用了。
循環(huán)檢測器能夠檢測垃圾回收循環(huán)并能回收它們。 gc 模塊提供了一種運(yùn)行該檢測器的方式 (collect() 函數(shù)),以及多個配置接口和在運(yùn)行時禁用該檢測器的功能。
1.10.1. Python中的引用計數(shù)
有兩個宏 Py_INCREF(x) 和 Py_DECREF(x) ,會處理引用計數(shù)的增減。 Py_DECREF() 也會在引用計數(shù)到達(dá)0時釋放對象。為了靈活,并不會直接調(diào)用 free() ,而是通過對象的 類型對象 的函數(shù)指針來調(diào)用。為了這個目的(或其他的),每個對象同時包含一個指向自身類型對象的指針。
最大的問題依舊:何時使用 Py_INCREF(x) 和 Py_DECREF(x) ?我們首先引入一些概念。沒有人”擁有”一個對象,你可以 擁有一個引用 到一個對象。一個對象的引用計數(shù)定義為擁有引用的數(shù)量。引用的擁有者有責(zé)任調(diào)用 Py_DECREF() ,在引用不再需要時。引用的擁有關(guān)系可以被傳遞。有三種辦法來處置擁有的引用:傳遞、存儲、調(diào)
本文標(biāo)題:創(chuàng)新互聯(lián)Python教程:1.使用C或C++擴(kuò)展Python
網(wǎng)頁地址:http://m.fisionsoft.com.cn/article/ccohssp.html


咨詢
建站咨詢
