構(gòu)建 Python C 擴(kuò)展模塊

有好幾種擴(kuò)展 Python 的功能的方法。其中一種就是用 C 或 C++ 編寫(xiě) Python 模塊。通過(guò)這個(gè)過(guò)程可以提高性能,更好地訪問(wèn) C 庫(kù)函數(shù)和系統(tǒng)調(diào)用。在本教程中,我將帶大家了解如何使用 Python API 來(lái)編寫(xiě) Python C 擴(kuò)展模塊。這里說(shuō)的都是 Cpython。
通過(guò)本教程你將學(xué)到
在 Python 內(nèi)部執(zhí)行 C 的函數(shù) 將參數(shù)通過(guò) Python 傳到 C 并依次解析它們 從 C 代碼中引發(fā)異常,并在 C 中創(chuàng)建自定義的 Python 異常 在 C 中定義全局常量,并在 Python 中訪問(wèn)它們 打包和發(fā)布Python C擴(kuò)展模塊
拓展你的 Python 程序
Python 的一個(gè)不太為人所知但卻非常強(qiáng)大的特性是,它能夠調(diào)用 C 或 C++ 等編譯語(yǔ)言定義的函數(shù)和庫(kù),擴(kuò)展 Python 內(nèi)置特性以外的功能。拓展 Python 功能有很多的語(yǔ)言可以選擇,那么為什么選擇 C 語(yǔ)言呢?以下是使用 C 構(gòu)建 Python 擴(kuò)展模塊的一些原因:
實(shí)現(xiàn)新的內(nèi)置對(duì)象類(lèi)型 其中一個(gè)就是 Python 底層就是 C 語(yǔ)言實(shí)現(xiàn)的,還有就是我們可以從Python 本身實(shí)例化和擴(kuò)展該類(lèi)。
調(diào)用 C 庫(kù)函數(shù)和系統(tǒng)調(diào)用 許多編程語(yǔ)言為最常用的系統(tǒng)調(diào)用提供接口,不過(guò),可能還有其他較少使用的系統(tǒng)調(diào)用只能通過(guò) C 訪問(wèn)。Python 中的
os模塊就是一個(gè)例子。
要用 C 語(yǔ)言編寫(xiě) Python 模塊,你需要了解 Python API,它定義了允許 Python 解釋器調(diào)用你的 C 代碼的各種函數(shù)、宏和變量。所有這些工具以及更多的工具都打包在Python.h頭文件中。
用 C 語(yǔ)言編寫(xiě) Python 接口
在本教程中,你將為一個(gè) C 庫(kù)函數(shù)編寫(xiě)一個(gè)包裝器,然后在 Python 中調(diào)用它。自己實(shí)現(xiàn)包裝器可以更好地了解何時(shí)以及如何使用 C 來(lái)擴(kuò)展 Python 模塊。
理解 C 語(yǔ)言中的 fputs()
**fputs()**就是我們將要包裝的 C 庫(kù)函數(shù)。下面是 fputs() 函數(shù)的聲明。
int?fputs(const?char?*str,?FILE?*stream)
這個(gè)函數(shù)有兩個(gè)參數(shù):
1.str :這是一個(gè)數(shù)組,包含了要寫(xiě)入的以空字符終止的字符序列。
2.stream * :這是指向 FILE 對(duì)象的指針,該 FILE 對(duì)象標(biāo)識(shí)了要被寫(xiě)入字符串的流,該函數(shù)返回一個(gè)非負(fù)值,如果發(fā)生錯(cuò)誤則返回 EOF。
下面的實(shí)例演示了 fputs() 函數(shù)的用法。
#include?
#include?
#include?
int?main()?{
????FILE?*fp?=?fopen("write.txt",?"w");
????fputs("I?Love?NightTeam!",?fp);
????fclose(fp);
????return?1;
}
簡(jiǎn)單總結(jié)上面的代碼:
1.打開(kāi)一個(gè)當(dāng)前目錄下叫 write.txt 的文件
2.然后在這個(gè)文件中寫(xiě)入 "I Love NightTeam!"
在下一節(jié)中,我們將為該函數(shù)提供更豐富的功能。
包裝 fputs() 函數(shù)
首先我們看最終完善之后的代碼
#include?
static?PyObject?*method_fputs(PyObject?*self,?PyObject?*args)?{
????//str 是要寫(xiě)入文件流的字符串。
????//filename 是要寫(xiě)入的文件的名稱。
????char?*str,?*filename?=?NULL;
????int?bytes_copied?=?-1;
????/*?Parse?arguments?*/
????if(!PyArg_ParseTuple(args,?"ss",?&str,?&filename))?{
????????return?NULL;
????}
????FILE?*fp?=?fopen(filename,?"w");
????bytes_copied?=?fputs(str,?fp);
????fclose(fp);
????return?PyLong_FromLong(bytes_copied);
}
接下來(lái),我們來(lái)一點(diǎn)點(diǎn)分析上面的代碼。我們這里面的代碼是按照Python API來(lái)寫(xiě)的,第一行
#include?
我們通過(guò)它導(dǎo)入 Python.h 這個(gè)頭文件,在 C 語(yǔ)言里是沒(méi)這個(gè)頭文件的,不過(guò)不用擔(dān)心,它在后期Python 運(yùn)行的時(shí)候會(huì)找到對(duì)應(yīng)的文件。這段代碼中引用了 Python.h 中定義的三個(gè)對(duì)象結(jié)構(gòu)。
1.PyObject
2.PyArg_ParseTuple()
3.PyLong_FromLong()
這些都是用于定義 Python 語(yǔ)言的數(shù)據(jù)類(lèi)型,開(kāi)頭都是 Py,現(xiàn)在我們一一來(lái)看。
PyObject
PyObject 是用于為 Python 定義對(duì)象的類(lèi)型。所有的 Python 對(duì)象都是在 PyObject 基礎(chǔ)上進(jìn)行拓展的,比如 Python 中的 int,在 C 語(yǔ)言中實(shí)際上是一個(gè) PyLongObject 函數(shù)。PyObject 告訴 Python 解釋器將指向?qū)ο蟮闹羔樢暈閷?duì)象。例如,將上述函數(shù)的返回類(lèi)型設(shè)置為 PyObject,這就定義了 Python 解釋器所需的公共字段。
PyArg_ParseTuple
PyArg_ParseTuple() 將從 Python 程序接收的參數(shù)解析為局部變量,返回一個(gè)整型。相關(guān)代碼片段
????if(!PyArg_ParseTuple(args,?"ss",?&str,?&filename))?{
????????return?NULL;
????}
它的語(yǔ)法是這樣的
int?PyArg_ParseTuple(PyObject*?tuple,char*?format,...)
1.args:參數(shù)arg必須是一個(gè)元組對(duì)象,包含一個(gè)從Python傳遞給C函數(shù)的參數(shù)列表
2."ss":是一個(gè)格式參數(shù)它必須是格式字符串,初次之外還有很多個(gè)參數(shù),最后面我會(huì)給出參考地址。
3.&str 和 &filename:可變參數(shù),指向局部變量的指針,解析后的值將賦給這些局部變量。這里我們的例子是 PyArg_ParseTuple() 如果執(zhí)行失敗結(jié)果為 false 。如函數(shù)將返回 NULL,不再繼續(xù)。
fputs()
如前所述,fputs()有兩個(gè)參數(shù),其中一個(gè)是 FILE * 對(duì)象。由于在 C 語(yǔ)言中無(wú)法使用 Python API 解析 Python textIOwrapper 對(duì)象,因此必須使用一種變通方法
????FILE?*fp?=?fopen(filename,?"w");?
????bytes_copied?=?fputs(str,?fp);?
????fclose(fp);
然后,將 fputs() 的返回值存儲(chǔ)在 bytes_copied 中。該整數(shù)變量將返回到 Python 解釋器中的fputs()調(diào)用
PyLong_FromLong(bytes_copied)
PyLong_FromLong() 返回一個(gè) PyLongObject,它在 Python 中表示一個(gè)整數(shù)對(duì)象。通過(guò)它將返回一個(gè) PyObject 對(duì)象給 Python。
編寫(xiě) Init 函數(shù)
我們已經(jīng)編寫(xiě)了構(gòu)成 Python C 擴(kuò)展模塊核心功能的代碼。但是,仍然需要一些額外的功能來(lái)啟動(dòng)和運(yùn)行模塊。需要編寫(xiě)模塊及其包含的方法的定義,如下所示:
static?PyMethodDef?FputsMethods[]?=?{
????{"fputs",?method_fputs,?METH_VARARGS,?"Python?interface?for?fputs?C?library?function"},
????{NULL,?NULL,?0,?NULL}
};
static?struct?PyModuleDef?fputsmodule?=?{
????PyModuleDef_HEAD_INIT,
????"fputs",
????"Python?interface?for?the?fputs?C?library?function",
????-1,
????FputsMethods
};
這些函數(shù)包括有關(guān)模塊的元信息,Python 解釋器將使用這些元信息。讓我們看看上面的每個(gè)結(jié)構(gòu)是如何工作的。
PyMethodDef
這是一個(gè)函數(shù)列表,因?yàn)槲覀円话銜?huì)定義多個(gè)函數(shù),使用 {NULL, NULL, 0, NULL} 表示最后一個(gè)函數(shù)。先看第一部分代碼
static?PyMethodDef?FputsMethods[]?=?{
????{"fputs",?method_fputs,?METH_VARARGS,?"Python?interface?for?fputs?C?library?function"},
????{NULL,?NULL,?0,?NULL}
};
函數(shù)列表的單個(gè)元素,由4個(gè)參數(shù)組成。第一個(gè)參數(shù)是用戶要調(diào)用的函數(shù)名稱,第二個(gè)是要調(diào)用的C函數(shù)名稱,第三個(gè)是模塊的標(biāo)示,告訴解釋器函數(shù)將接受兩個(gè) PyObject 類(lèi)型的參數(shù),self 模塊對(duì)象和arg 函數(shù)的實(shí)際參數(shù)的元組。第四個(gè)就是函數(shù)的 docstring ,我們可以通過(guò) help(fputs) 獲取。
PyModuleDef
正如 PyMethodDef 保留有關(guān) Python C 擴(kuò)展模塊中方法的信息一樣,PyModuleDef 結(jié)構(gòu)也保留有關(guān)模塊本身的信息。但是它不是結(jié)構(gòu)的數(shù)組,而是用于模塊定義的單個(gè)結(jié)構(gòu)。
static?struct?PyModuleDef?fputsmodule?=?{
????PyModuleDef_HEAD_INIT,
????"fputs",
????"Python?interface?for?the?fputs?C?library?function",
????-1,
????FputsMethods
};
第一個(gè)參數(shù)固定寫(xiě)就可以了,第二個(gè)參數(shù)是 Python C 擴(kuò)展模塊的名稱。第三個(gè)參數(shù)表示模塊docstring 的值。第四個(gè)參數(shù)模塊空間,一般子解釋器使用的,-1 表示不使用,第五個(gè)參數(shù)就是上面定義的函數(shù)列表。
PyMODINIT_FUNC
既然已經(jīng)定義了 Python C 擴(kuò)展模塊和方法結(jié)構(gòu),現(xiàn)在就該使用它們了。當(dāng) Python 程序第一次導(dǎo)入模塊時(shí),它將調(diào)用 PyInit_fputs()
PyMODINIT_FUNC?PyInit_fputs(void)?{
????return?PyModule_Create(&fputsmodule);
}
PyMODINIT_FUNC 在聲明為函數(shù)返回類(lèi)型時(shí)隱式地做了三件事:1.它將函數(shù)的返回類(lèi)型隱式設(shè)置為 PyObject *。2.它聲明任何特殊的鏈接。3.它將函數(shù)聲明為 extern C。如果你在使用 C++,它會(huì)告訴 C++ 編譯器以 C 的方式運(yùn)行。PyInit_ 作為固定開(kāi)頭,然后加模塊的名字 fputs。PyModule_Create() 將返回一個(gè)類(lèi)型為 PyObject * 的新模塊對(duì)象。參數(shù)傳入的是上面定義的fputsmodule。
注意:在 Python3 中,你的 init 函數(shù)必須返回一個(gè) PyObject * 類(lèi)型。但是,如果使用的是Python2,那么 PyMODINIT_FUNC 將函數(shù)返回類(lèi)型聲明為 void。
回顧整個(gè)過(guò)程
現(xiàn)在我們已經(jīng)編寫(xiě)了 Python C 擴(kuò)展模塊的必要部分,讓我們回過(guò)頭來(lái)看看它們是如何組合在一起的。下圖顯示了模塊的組件以及它們?nèi)绾闻c Python 解釋器交互
當(dāng)你通過(guò) Python 導(dǎo)入 fputs 模塊的使用,首先會(huì)進(jìn)入 PyInit_fputs 這個(gè)入口函數(shù),在將引用返回給 Python 解釋器之前,該函數(shù)隨后調(diào)用 PyModule_Create(),它將初始化 PyModuleDef 和 PyMethodDef 函數(shù),其中包含關(guān)于模塊的元信息。準(zhǔn)備好它們是有意義的,因?yàn)槟銓⒃?init 函數(shù)中使用它們。完成之后,對(duì)模塊對(duì)象的引用最終返回給 Python 解釋器。下圖顯示了模塊的內(nèi)部流程
PyModule_Create() 返回的模塊對(duì)象有一個(gè)對(duì)模塊結(jié)構(gòu) PyModuleDef 的引用,該結(jié)構(gòu)又有一個(gè)對(duì)方法 PyMethodDef 的引用。當(dāng)你調(diào)用在 Python C 擴(kuò)展模塊中定義的方法時(shí),Python 解釋器使用模塊對(duì)象及其攜帶的所有引用來(lái)執(zhí)行特定的方法。同樣,你可以訪問(wèn)模塊的各種其他方法和屬性,例如模塊 docstring 或方法 docstring。這些定義在它們各自的結(jié)構(gòu)內(nèi)部。
現(xiàn)在你已經(jīng)了解了從 Python 解釋器調(diào)用 fputs() 時(shí)會(huì)發(fā)生什么,解釋器使用模塊對(duì)象以及模塊和方法引用來(lái)調(diào)用方法。最后,讓我們看看解釋器如何處理 Python C 擴(kuò)展模塊運(yùn)行的:
調(diào)用 fputs() 方法后,程序?qū)?zhí)行以下步驟:
1.使用 PyArg_ParseTuple() 解析從 Python 解釋器傳遞的參數(shù)
2.將這些參數(shù)傳遞給 fputs(),這是構(gòu)成模塊核心的 C 庫(kù)函數(shù)。
3.使用 PyLong_FromLong 從 fput() 返回值
最后是完整代碼
#include?
static?PyObject?*method_fputs(PyObject?*self,?PyObject?*args)?{
????//str是要寫(xiě)入ss文件流的字符串。
????//filename是要寫(xiě)入的文件的名稱。
????char?*str,?*filename?=?NULL;
????int?bytes_copied?=?-1;
????/*?Parse?arguments?*/
????if(!PyArg_ParseTuple(args,?"ss",?&str,?&filename))?{
????????return?NULL;
????}
????FILE?*fp?=?fopen(filename,?"w");
????bytes_copied?=?fputs(str,?fp);
????fclose(fp);
????return?PyLong_FromLong(bytes_copied);
}
static?PyMethodDef?FputsMethods[]?=?{
????{"fputs",?method_fputs,?METH_VARARGS,?"Python?interface?for?fputs?C?library?function"},
????{NULL,?NULL,?0,?NULL}
};
static?struct?PyModuleDef?fputsmodule?=?{
????PyModuleDef_HEAD_INIT,
????"fputs",
????"Python?interface?for?the?fputs?C?library?function",
????-1,
????FputsMethods
};
PyMODINIT_FUNC?PyInit_fputs(void)?{
????return?PyModule_Create(&fputsmodule);
}
打包 Python C 擴(kuò)展模塊
在導(dǎo)入新模塊之前,首先需要構(gòu)建它。可以通過(guò)使用 Python 的 distutils 模塊實(shí)現(xiàn)這一點(diǎn)。下面先上代碼,文件名setup.py
from?distutils.core?import?setup,?Extension
def?main():
????setup(name="fputs",
??????????version="1.0.0",
??????????description="Python?interface?for?the?fputs?C?library?function",
??????????author="cxa",
??????????author_email="[email protected]",
??????????ext_modules=[Extension("fputs",?["fputsmodule.c"])])
if?__name__?==?"__main__":
????main()
代碼很簡(jiǎn)單,我主要是解釋下 setup 里面的參數(shù)函數(shù)含義, name 就是打包文件名稱,version 版本號(hào),一般都是 1.0.0 開(kāi)始的。description 就是模塊描述,ext_modules 是一個(gè)數(shù)組類(lèi)型,Extension("fputs", ["fputsmodule.c"]),Extension里面第一個(gè)參數(shù)是模塊,第二個(gè)參數(shù)注意它是一個(gè)列表類(lèi)型。它表示的是我們編寫(xiě)好的 C 文件的路徑。
構(gòu)建模塊
現(xiàn)在你已經(jīng)有了 setup.py 文件,可以使用它來(lái)構(gòu)建 Python C 擴(kuò)展模塊了。構(gòu)建非常簡(jiǎn)單一句話就可以了
python3?setup.py?install
該命令將編譯并安裝當(dāng)前目錄下的Python C擴(kuò)展模塊。如果失敗了就根據(jù)具體錯(cuò)誤信息,百度搜下就可以解決了。
運(yùn)行你的模塊
現(xiàn)在一切都就緒了,是時(shí)候看看你的模塊是如何工作的了!
>>>?import?fputs
>>>?fputs.__doc__
'Python?interface?for?the?fputs?C?library?function'
>>>?fputs.__name__
'fputs'
>>>?#?Write?to?an?empty?file?named?`write.txt`
>>>?fputs.fputs("NightTeam!",?"write.txt")
13
>>>?with?open("write.txt",?"r")?as?f:
>>>?????print(f.read())
'NightTeam!'

引發(fā)異常
Python 異常與 C++ 異常非常不同。如果希望從 C 擴(kuò)展模塊中引發(fā) Python 異常,那么可以使用Python API 來(lái)實(shí)現(xiàn)。Python API 提供的一些用于異常引發(fā)的函數(shù)如下
| 函數(shù)名 | 描述 |
|---|---|
| PyErr_SetString(PyObject *type,?const char *message) | 帶有兩個(gè)參數(shù):一個(gè)PyObject *類(lèi)型的參數(shù),指定異常的類(lèi)型,以及一個(gè)向用戶顯示的自定義消息 |
| PyErr_Format(PyObject *type,const char *format) | 帶有兩個(gè)參數(shù):一個(gè)PyObject *類(lèi)型的參數(shù),指定異常的類(lèi)型,以及一個(gè)向用戶顯示的格式化自定義消息 |
| PyErr_SetObject(PyObject *type,?PyObject *value) | 接受兩個(gè)參數(shù),都是PyObject *類(lèi)型:第一個(gè)參數(shù)指定異常的類(lèi)型,第二個(gè)參數(shù)設(shè)置一個(gè)任意的Python對(duì)象作為異常值 |
你可以使用其中任何一個(gè)來(lái)引發(fā)異常。但是,使用哪一個(gè)以及何時(shí)使用完全取決具體的需求。Python API擁有所有預(yù)先定義為PyObject類(lèi)型的標(biāo)準(zhǔn)異常。
從C代碼中引發(fā)異常
雖然在C語(yǔ)言中不能引發(fā)異常,但Python API允許你從Python C擴(kuò)展模塊中引發(fā)異常。我們通過(guò)向代碼中添加PyErr_SetString()來(lái)測(cè)試這個(gè)功能。
static?PyObject?*method_fputs(PyObject?*self,?PyObject?*args)?{
????char?*str,?*filename?=?NULL;
????int?bytes_copied?=?-1;
????/*?Parse?arguments?*/
????if(!PyArg_ParseTuple(args,?"ss",?&str,?&fd))?{
????????return?NULL;
????}
????if?(strlen(str)?10)?{
????????PyErr_SetString(PyExc_ValueError,?"String?length?must?be?greater?than?10");
????????return?NULL;
????}
????fp?=?fopen(filename,?"w");
????bytes_copied?=?fputs(str,?fp);
????fclose(fp);
????return?PyLong_FromLong(bytes_copied);
}
在這里,在解析參數(shù)之后和調(diào)用 fputs() 之前,檢查輸入字符串的長(zhǎng)度。如果用戶傳遞的字符串小于10 個(gè)字符,則程序?qū)⑹褂米远x消息引發(fā) ValueError 錯(cuò)誤。一旦異常發(fā)生,程序執(zhí)行就會(huì)停止。注意上面的 fputs() 方法在引發(fā)異常后返回了一個(gè) NULL。這是因?yàn)橹灰闶褂?PyErr_*()引發(fā)異常。不需要調(diào)用函數(shù)來(lái)隨后再次設(shè)置該條目。因此,調(diào)用函數(shù)返回一個(gè)指示失敗的值,通常為NULL或-1。(這也應(yīng)該解釋為什么當(dāng)使用 PyArg_ParseTuple()解析 method_fputs()中的參數(shù)時(shí),為什么需要返回 NULL。)
增加自定義異常
你還可以在 Python C 擴(kuò)展模塊中引發(fā)自定義異常。但是,使用方法和上面有所不同。在前面的PyMODINIT_FUNC 中,你只需返回由 PyModule_Create 返回的實(shí)例即可。但是如果讓使用模塊的用戶能夠訪問(wèn)自定義異常,就需要在返回之前將自定義異常添加到模塊實(shí)例。
static?PyObject?*StringTooShortError?=?NULL;
PyMODINIT_FUNC?PyInit_fputs(void)?{
????/*?分配模塊值?*/
????PyObject?*module?=?PyModule_Create(&fputsmodule);
????/*?初始化新的異常對(duì)象?*/
????StringTooShortError?=?PyErr_NewException("fputs.StringTooShortError",?NULL,?NULL);
????/*?將異常對(duì)象添加到模塊中?*/
????PyModule_AddObject(module,?"StringTooShortError",?StringTooShortError);
????return?module;
}
與前面一樣,首先創(chuàng)建一個(gè)模塊對(duì)象。然后使用 PyErr_NewException 創(chuàng)建一個(gè)新的異常對(duì)象。第一個(gè)參數(shù)采用 module.classname 的形式作為要?jiǎng)?chuàng)建的異常類(lèi)的名稱,選擇描述性內(nèi)容,以使用戶更容易解釋實(shí)際出了什么問(wèn)題。接下來(lái),使用 PyModule_AddObject 將其添加到模塊對(duì)象中。第一個(gè)參數(shù)是上面創(chuàng)建的模塊對(duì)象,第二個(gè)參數(shù)是異常對(duì)象的名稱,第三個(gè)參數(shù) 就是異常對(duì)象本身。最后返回模塊對(duì)象。
既然已經(jīng)定義了新的異常方法,那么我們就可以將核心代碼改為下面這樣:
static?PyObject?*method_fputs(PyObject?*self,?PyObject?*args)?{
????char?*str,?*filename?=?NULL;
????int?bytes_copied?=?-1;
????/*?Parse?arguments?*/
????if(!PyArg_ParseTuple(args,?"ss",?&str,?&fd))?{
????????return?NULL;
????}
????if?(strlen(str)?10)?{
????????/*?Passing?custom?exception?*/
????????PyErr_SetString(StringTooShortError,?"String?length?must?be?greater?than?10");
????????return?NULL;
????}
????fp?=?fopen(filename,?"w");
????bytes_copied?=?fputs(str,?fp);
????fclose(fp);
????return?PyLong_FromLong(bytes_copied);
}
之后打包,構(gòu)建生成新的模塊。通過(guò)下面的代碼進(jìn)行測(cè)試
>>>?import?fputs
>>>?#?Custom?exception
>>>?fputs.fputs("NT!",?fp.fileno())
Traceback?(most?recent?call?last):
??File?"" ,?line?1,?in?
fputs.StringTooShortError:?String?length?must?be?greater?than?10
如果字符串長(zhǎng)度小于 10,這個(gè)時(shí)候我們定義異常就會(huì)拋出了。
定義常量
在某些情況下,需要在 Python C 擴(kuò)展模塊中使用或定義常量。這與您在前一節(jié)中定義自定義異常的方式非常相似??梢允褂?PyModule_AddIntConstant() 定義一個(gè)新常量并將其添加到模塊實(shí)例中。
PyMODINIT_FUNC?PyInit_fputs(void)?{
????/*?Assign?module?value?*/
????PyObject?*module?=?PyModule_Create(&fputsmodule);
????/*?Add?int?constant?by?name?*/
????PyModule_AddIntConstant(module,?"FPUTS_FLAG",?64);
????/*?Define?int?macro?*/
????#define?FPUTS_MACRO?256
????/*?Add?macro?to?module?*/
????PyModule_AddIntMacro(module,?FPUTS_MACRO);
????return?module;
}
其中
????PyModule_AddIntConstant(module,?"FPUTS_FLAG",?64);
里面包含三個(gè)參數(shù),分別是模塊的名字,常量的名稱和常量的值。你還可以使用 PyModule_AddIntMacro() 對(duì)宏執(zhí)行相同的操作。
????/*?定義宏?*/
????#define?FPUTS_MACRO?256
????/*?添加宏到模塊*/
????PyModule_AddIntMacro(module,?FPUTS_MACRO);
重新打包構(gòu)建并運(yùn)行觀察結(jié)果
>>>?import?fputs
>>>?#?Constants
>>>?fputs.FPUTS_FLAG
64
>>>?fputs.FPUTS_MACRO
256
我們發(fā)現(xiàn),可以從Python解釋器中訪問(wèn)這些常量。
考慮替代方案
在本教程中,你已經(jīng)為C庫(kù)函數(shù)構(gòu)建了一個(gè)接口,以了解如何編寫(xiě) Python C 擴(kuò)展模塊。但是,有時(shí)你需要做的只是調(diào)用一些系統(tǒng)調(diào)用或一些C庫(kù)函數(shù),并且希望避免編寫(xiě)兩種不同語(yǔ)言的開(kāi)銷(xiāo)。在這些情況下,你可以使用 Python 庫(kù),如 ctypes 或 cffi。關(guān)于 ctypes 是的使用可以看我公眾號(hào)之前寫(xiě)的文章。
總結(jié)
在本教程中,你學(xué)習(xí)了如何使用 Python API 以 C 編程語(yǔ)言編寫(xiě) Python 接口。為 C 庫(kù)函數(shù)fputs() 編寫(xiě)了一個(gè) Python 包裝器。在構(gòu)建之前,我們還向模塊添加了自定義異常和常量。
Python API 為用 C 編程語(yǔ)言編寫(xiě)復(fù)雜的 Python 接口提供了大量特性。同時(shí),像 cffi 或ctypes 這樣的庫(kù)可以降低編寫(xiě) Python C 擴(kuò)展模塊所涉及的開(kāi)銷(xiāo)。所以應(yīng)該按照自己的需求選擇合理的拓展方式。
參考資料
https://realpython.com/build-python-c-extension-module/
https://www.oreilly.com/library/view/python-in-a/0596001886/re1107.html
