<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ### 導航 - [索引](../genindex.xhtml "總目錄") - [模塊](../py-modindex.xhtml "Python 模塊索引") | - [下一頁](newtypes_tutorial.xhtml "2. 自定義擴展類型:教程") | - [上一頁](index.xhtml "擴展和嵌入 Python 解釋器") | - ![](https://box.kancloud.cn/a721fc7ec672275e257bbbfde49a4d4e_16x16.png) - [Python](https://www.python.org/) ? - zh\_CN 3.7.3 [文檔](../index.xhtml) ? - [擴展和嵌入 Python 解釋器](index.xhtml) ? - $('.inline-search').show(0); | # 1. 使用 C 或 C++ 擴展 Python 如果你會用 C,添加新的 Python 內置模塊會很簡單。以下兩件不能用 Python 直接做的事,可以通過 *extension modules* 來實現:實現新的內置對象類型;調用 C 的庫函數和系統調用。 為了支持擴展,Python API(應用程序編程接口)定義了一系列函數、宏和變量,可以訪問 Python 運行時系統的大部分內容。Python 的 API 可以通過在一個 C 源文件中引用 `"Python.h"` 頭文件來使用。 擴展模塊的編寫方式取決與你的目的以及系統設置;下面章節會詳細介紹。 注解 C擴展接口特指CPython,擴展模塊無法在其他Python實現上工作。在大多數情況下,應該避免寫C擴展,來保持可移植性。舉個例子,如果你的用例調用了C庫或系統調用,你應該考慮使用 [`ctypes`](../library/ctypes.xhtml#module-ctypes "ctypes: A foreign function library for Python.") 模塊或 [cffi](https://cffi.readthedocs.io/) \[https://cffi.readthedocs.io/\] 庫,而不是自己寫C代碼。這些模塊允許你寫Python代碼來接口C代碼,而且可移植性更好。不知為何編譯失敗了。 ## 1.1. 一個簡單的例子 讓我們創建一個擴展模塊 `spam` (Monty Python 粉絲最喜歡的食物...) 并且想要創建對應 C 庫函數 `system()` [1](#id5) 的 Python 接口。 這個函數接受一個以 null 結尾的字符串參數并返回一個整數。 我們希望可以在 Python 中以如下方式調用此函數: ``` >>> import spam >>> status = spam.system("ls -l") ``` 首先創建一個 `spammodule.c` 文件。(傳統上,如果一個模塊叫 `spam`,則對應實現它的 C 文件叫 `spammodule.c`;如果這個模塊名字非常長,比如 `spammify`,則這個模塊的文件可以直接叫 `spammify.c`。) 文件中開始的兩行是: ``` #define PY_SSIZE_T_CLEAN #include <Python.h> ``` 這會導入 Python API(如果你喜歡,你可以在這里添加描述模塊目標和版權信息的注釋)。 注解 由于Python可能會定義一些影響某些系統上標準頭文件的預處理器定義,因此在包含任何標準頭文件之前,您\*必須\* include 這個文件:Python.h。 推薦總是在 `Python.h` 前定義 `PY_SSIZE_T_CLEAN` 。查看 [提取擴展函數的參數](#parsetuple) 來了解這個宏的更多內容。 所有用戶可見的符號都定義自 `Python.h` 中,并擁有前綴 `Py` 或 `PY` ,除了那些已經定義在標準頭文件的。 為了方便,以及由于其在 Python 解釋器中廣泛應用,`"Python.h"` 也包含了少量標準頭文件: `<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); } ``` 有個直接翻譯參數列表的方法(例如單獨的 `“ls-l"`)到要傳遞給C函數的參數。C函數總是有兩個參數,通常名字是 *self* 和 *args* 。 對模塊級函數, *self* 參數指向模塊對象;對于對象實例則指向方法。 *args* 參數是指向一個 Python 的 tuple 對象的指針,其中包含參數。 每個 tuple 項對應一個調用參數。 這些參數也全都是 Python 對象 --- 要在我們的 C 函數中使用它們就需要先將其轉換為 C 值。 Python API 中的函數 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 會檢查參數類型并將其轉換為 C 值。 它使用模板字符串確定需要的參數類型以及存儲被轉換的值的 C 變量類型。 細節將稍后說明。 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 正常返回非零,并已經按照提供的地址存入了各個變量值。如果出錯(零)則應該讓函數返回NULL以通知解釋器出錯(有如例子中看到的)。 ## 1.2. 關于錯誤和異常 一個Python解釋器的常見慣例是,函數發生錯誤時,應該設置一個異常環境并返回錯誤值(通常是 *NULL* 指針)。異常存儲在解釋器靜態全局變量中,如果為 *NULL* ,則沒有發生異常。異常的第一個參數也需要保存在靜態全局變量中,也就是raise的第二個參數(第二個參數到 [`raise`](../reference/simple_stmts.xhtml#raise))。第三個變量包含棧回溯信息。這三個變量等同于Python變量 [`sys.exc_info()`](../library/sys.xhtml#sys.exc_info "sys.exc_info") (查看Python庫參考的模塊 [`sys`](../library/sys.xhtml#module-sys "sys: Access system-specific parameters and functions.") 的章節)。這對于理解到底發生了什么錯誤是很重要的。 Python API中定義了一些函數來設置這些變量。 最常用的就是 [`PyErr_SetString()`](../c-api/exceptions.xhtml#c.PyErr_SetString "PyErr_SetString")。 其參數是異常對象和 C 字符串。 異常對象一般是像 `PyExc_ZeroDivisionError` 這樣的預定義對象。 C 字符串指明異常原因,并被轉換為一個 Python 字符串對象存儲為異常的“關聯值”。 另一個有用的函數是 [`PyErr_SetFromErrno()`](../c-api/exceptions.xhtml#c.PyErr_SetFromErrno "PyErr_SetFromErrno") ,僅接受一個異常對象,異常描述包含在全局變量 `errno` 中。最通用的函數還是 [`PyErr_SetObject()`](../c-api/exceptions.xhtml#c.PyErr_SetObject "PyErr_SetObject") ,包含兩個參數,分別為異常對象和異常描述。你不需要使用 [`Py_INCREF()`](../c-api/refcounting.xhtml#c.Py_INCREF "Py_INCREF") 來增加傳遞到其他函數的參數對象的引用計數。 你可以通過 [`PyErr_Occurred()`](../c-api/exceptions.xhtml#c.PyErr_Occurred "PyErr_Occurred") 獲知當前異常,返回當前異常對象,如果確實沒有則為 *NULL* 。一般來說,你在調用函數時不需要調用 [`PyErr_Occurred()`](../c-api/exceptions.xhtml#c.PyErr_Occurred "PyErr_Occurred") 檢查是否發生了異常,你可以直接檢查返回值。 當函數 *f* 調用另一個函數 *g* 時檢測到后者出錯了,*f* 自身將返回一個錯誤值 (通常為 *NULL* 或 `-1`)。 它 *不應* 調用某個 `PyErr_*()` 函數 --- 這種函數已經由 *g* 調用過了。 然后 *f* 的調用者也應該返回一個錯誤提示 *它的* 調用者,同樣 *不應* 調用 `PyErr_*()` ,依此類推 --- 錯誤的最詳細原因已經由首先檢測到它的函數報告了。 一旦這個錯誤到達了 Python 解釋器的主循環,它將中斷當前執行的 Python 代碼并嘗試找到由 Python 程序員所指定的異常處理。 (在某些情況下,當模塊確實能夠通過調用其它 `PyErr_*()` 函數給出更加詳細的錯誤消息,并且在這些情況是可以這樣做的。 但是按照一般規則,這是不必要的,并可能導致有關錯誤原因的信息丟失:大多數操作會由于種種原因而失敗。) 想要忽略由一個失敗的函數調用所設置的異常,異常條件必須通過調用 [`PyErr_Clear()`](../c-api/exceptions.xhtml#c.PyErr_Clear "PyErr_Clear") 顯式地被清除。 C 代碼應當調用 [`PyErr_Clear()`](../c-api/exceptions.xhtml#c.PyErr_Clear "PyErr_Clear") 的唯一情況是如果它不想將錯誤傳給解釋器而是想完全由自己來處理它(可能是嘗試其他方法,或是假裝沒有出錯)。 每次失敗的 `malloc()` 調用必須轉換為一個異常。 `malloc()` (或 `realloc()` )的直接調用者必須調用 [`PyErr_NoMemory()`](../c-api/exceptions.xhtml#c.PyErr_NoMemory "PyErr_NoMemory") 來返回錯誤來提示。所有對象創建函數(例如 [`PyLong_FromLong()`](../c-api/long.xhtml#c.PyLong_FromLong "PyLong_FromLong") )已經這么做了,所以這個提示僅用于直接調用 `malloc()` 的情況。 還要注意的是,除了 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 等重要的例外,返回整數狀態碼的函數通常都是返回正值或零來表示成功,而以 `-1` 表示失敗,如同 Unix 系統調用一樣。 最后,當你返回一個錯誤指示器時要注意清理垃圾(通過為你已經創建的對象執行 [`Py_XDECREF()`](../c-api/refcounting.xhtml#c.Py_XDECREF "Py_XDECREF") 或 [`Py_DECREF()`](../c-api/refcounting.xhtml#c.Py_DECREF "Py_DECREF") 調用)! 選擇引發哪個異常完全取決于你的喜好。 所有內置的 Python 異常都有對應的預聲明 C 對象,例如 `PyExc_ZeroDivisionError`,你可以直接使用它們。 當然,你應當明智地選擇異常 --- 不要使用 `PyExc_TypeError` 來表示一個文件無法被打開 (那大概應該用 `PyExc_IOError`)。 如果參數列表有問題,[`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "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()`](../c-api/exceptions.xhtml#c.PyErr_NewException "PyErr_NewException") 函數使用 [`Exception`](../library/exceptions.xhtml#Exception "Exception") 為基類創建一個類(除非是使用另外一個類替代 *NULL* )。描述參考 [內置異常](../library/exceptions.xhtml#bltin-exceptions) 。 同樣注意的是創建類保存了 `SpamError` 的一個引用,這是有意的。為了防止被垃圾回收掉,否則 `SpamError` 隨時會成為野指針。 一會討論 `PyMODINIT_FUNC` 作為函數返回類型的用法。 `spam.error` 異常可以在擴展模塊中拋出,通過 [`PyErr_SetString()`](../c-api/exceptions.xhtml#c.PyErr_SetString "PyErr_SetString") 函數調用,如下: ``` 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. 回到例子 回到前面的例子,你應該明白下面的代碼: ``` if (!PyArg_ParseTuple(args, "s", &command)) return NULL; ``` 如果在參數列表中檢測到錯誤,將會返回 *NULL* (返回對象指針的函數的錯誤指示器) , 依據 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 所設置的異常。 在其他情況下參數的字符串值會被拷貝到局部變量 `command`。 這是一個指針賦值,你不應該修改它所指向的字符串 (所以在標準 C 中,變量 `command` 應當被正確地聲明為 `const char *command`)。 下一個語句使用UNIX系統函數 `system()` ,傳遞給他的參數是剛才從 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 取出的: ``` sts = system(command); ``` 我們的 `spam.system()` 函數必須返回 `sts` 的值作為Python對象。這通過使用函數 [`PyLong_FromLong()`](../c-api/long.xhtml#c.PyLong_FromLong "PyLong_FromLong") 來實現。 ``` return PyLong_FromLong(sts); ``` 在這種情況下,會返回一個整數對象,(這個對象會在Python堆里面管理)。 如果你的C函數沒有有用的返回值(返回 `void` 的函數),則必須返回 `None` 。(你可以用 `Py_RETUN_NONE` 宏來完成): ``` Py_INCREF(Py_None); return Py_None; ``` [`Py_None`](../c-api/none.xhtml#c.Py_None "Py_None") 是一個C名字指定Python對象 `None` 。這是一個真正的PY對象,而不是 *NULL* 指針。 ## 1.4. 模塊方法表和初始化函數 為了展示 `spam_system()` 如何被Python程序調用。把函數聲明為可以被Python調用,需要先定義一個方法表 "method table" 。 ``` 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` 。值 `0` 代表使用 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 的陳舊變量。 如果單獨使用 `METH_VARARGS` ,函數會等待Python傳來tuple格式的參數,并最終使用 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 進行解析。 [`METH_KEYWORDS`](../c-api/structures.xhtml#METH_KEYWORDS "METH_KEYWORDS") 值表示接受關鍵字參數。這種情況下C函數需要接受第三個 `PyObject *` 對象,表示字典參數,使用 [`PyArg_ParseTupleAndKeywords()`](../c-api/arg.xhtml#c.PyArg_ParseTupleAndKeywords "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()` 被調用。(查看后續注釋了解嵌入Python)。他會調用 [`PyModule_Create()`](../c-api/module.xhtml#c.PyModule_Create "PyModule_Create") ,會返回模塊對象,并插入內置函數對象到新創建的模塊里,基于表(一個 [`PyMethodDef`](../c-api/structures.xhtml#c.PyMethodDef "PyMethodDef") 結構體的數組類型)到模塊定義。 [`PyModule_Create()`](../c-api/module.xhtml#c.PyModule_Create "PyModule_Create") 返回一個指向剛創建模塊的指針。也可能因為嚴重錯誤而中止,或返回 *NULL* 在模塊無法初始化成功時。初始化函數必須返回模塊對象給調用者,所以之后會被插入 `sys.modules` 。 當嵌入Python時, `PyInit_spam()` 函數不會被自動調用,除非放在 `PyImport_Inittab` 表里。要添加模塊到初始化表,使用 [`PyImport_AppendInittab()`](../c-api/import.xhtml#c.PyImport_AppendInittab "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` 中。這些文件可以用作你的代碼模板,或者學習。腳本 modulator.py 包含在源碼發行版或Windows安裝中,提供了一個簡單的GUI,用來聲明需要實現的函數和對象,并且可以生成供填入的模板。腳本在 Tools/modulator/ 目錄。查看README以了解用法。 注解 不像我們的 `spam` 例子, `xxmodule` 使用了 *多階段初始化* (Python3.5開始引入), `PyInit_spam` 會返回一個 PyModuleDef 結構體,然后創建的模塊放到導入機制。細節參考 [**PEP 489**](https://www.python.org/dev/peps/pep-0489) \[https://www.python.org/dev/peps/pep-0489\] 的多階段初始化。 ## 1.5. 編譯和鏈接 在你能使用你的新寫的擴展之前,你還需要做兩件事情:使用 Python 系統來編譯和鏈接。如果你使用動態加載,這取決于你使用的操作系統的動態加載機制;更多信息請參考編譯擴展模塊的章節( [構建C/C++擴展](building.xhtml#building) 章節),以及在 Windows 上編譯需要的額外信息( [在Windows平臺編譯C和C++擴展](windows.xhtml#building-on-windows) 章節)。 如果你不使用動態加載,或者想要讓模塊永久性的作為Python解釋器的一部分,就必須修改配置設置,并重新構建解釋器。幸運的是在Unix上很簡單,只需要把你的文件 ( `spammodule.c` 為例) 放在解壓縮源碼發行包的 `Modules/` 目錄下,添加一行到 `Modules/Setup.local` 來描述你的文件: ``` spam spammodule.o ``` 然后在頂層目錄運行 **make** 來重新構建解釋器。你也可以在 `Modules/` 子目錄使用 **make**,但是你必須先重建 `Makefile` 文件,然后運行 '**make** Makefile' 命令。(你每次修改 `Setup` 文件都需要這樣操作。) 如果你的模塊需要額外的鏈接,這些內容可以列出在配置文件里,舉個實例: ``` spam spammodule.o -lX11 ``` ## 1.6. 在C中調用Python函數 迄今為止,我們一直把注意力集中于讓Python調用C函數,其實反過來也很有用,就是用C調用Python函數。這在回調函數中尤其有用。如果一個C接口使用回調,那么就要實現這個回調機制。 幸運的是,Python解釋器是比較方便回調的,并給標準Python函數提供了標準接口。(這里就不再詳述解析Python代碼作為輸入的方式,如果有興趣可以參考 `Python/pythonmain.c` 中的 [`-c`](../using/cmdline.xhtml#cmdoption-c) 命令代碼。) 調用Python函數,首先Python程序要傳遞Python函數對象。應該提供個函數(或其他接口)來實現。當調用這個函數時,用全局變量保存Python函數對象的指針,還要調用 ([`Py_INCREF()`](../c-api/refcounting.xhtml#c.Py_INCREF "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`](../c-api/structures.xhtml#METH_VARARGS "METH_VARARGS") 標志注冊到解釋器,這在 [模塊方法表和初始化函數](#methodtable) 章節會描述。 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 函數及其參數的文檔在 [提取擴展函數的參數](#parsetuple) 。 [`Py_XINCREF()`](../c-api/refcounting.xhtml#c.Py_XINCREF "Py_XINCREF") 和 [`Py_XDECREF()`](../c-api/refcounting.xhtml#c.Py_XDECREF "Py_XDECREF") 這兩個宏可以用來增加或減少對象的引用計數,即使參數是 *NULL* 指針,操作也是安全的(但在這個例子中 *temp* 永遠不會為 *NULL*)。更多內容請參考 [引用計數](#refcounts) 段落。 `PyEval_CallObject()` 返回一個Python對象指針表示返回值。該函數有2個參數,都是指向Python對象的指針:Python函數,和參數列表。參數列表必須是tuple對象,其長度是參數數量。要調用無參數的Python函數,可以傳遞NULL或空元組。要用唯一參數調用,傳遞單一元組。 [`Py_BuildValue()`](../c-api/arg.xhtml#c.Py_BuildValue "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()`](../c-api/object.xhtml#c.PyObject_CallObject "PyObject_CallObject") 返回Python對象指針,這也是Python函數的返回值。 [`PyObject_CallObject()`](../c-api/object.xhtml#c.PyObject_CallObject "PyObject_CallObject") 是一個對其參數 "引用計數無關" 的函數。例子中新的元組創建用于參數列表,并且在 [`PyObject_CallObject()`](../c-api/object.xhtml#c.PyObject_CallObject "PyObject_CallObject") 之后立即使用了 [`Py_DECREF()`](../c-api/refcounting.xhtml#c.Py_DECREF "Py_DECREF") 。 `PyEval_CallObject()` 的返回值總是“新”的:要么是一個新建的對象;要么是已有對象,但增加了引用計數。所以除非你想把結果保存在全局變量中,你需要對這個值使用 [`Py_DECREF()`](../c-api/refcounting.xhtml#c.Py_DECREF "Py_DECREF"),即使你對里面的內容(特別!)不感興趣。 在你這么做之前,需要先檢查返回值是否是 *NULL* 。如果是,Python函數會終止并拋出異常。如果C代碼調用了從Python傳入的函數 [`PyObject_CallObject()`](../c-api/object.xhtml#c.PyObject_CallObject "PyObject_CallObject") ,因該立即返回錯誤來告知Python調用者,然后解釋器會打印棧回溯,或者調用Python代碼來處理這個異常。如果無法處理,異常會被 [`PyErr_Clear()`](../c-api/exceptions.xhtml#c.PyErr_Clear "PyErr_Clear") 清除,例如: ``` if (result == NULL) return NULL; /* Pass error back */ ...use result... Py_DECREF(result); ``` 依賴于具體的回調函數,你還要提供一個參數列表到 `PyEval_CallObject()` 。在某些情況下參數列表是由Python程序提供的,通過接口再傳到回調函數。這樣就可以不改變形式直接傳遞。另外一些時候你要構造一個新的tuple來傳遞參數。最簡單的方法就是 [`Py_BuildValue()`](../c-api/arg.xhtml#c.Py_BuildValue "Py_BuildValue") 函數構造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)` 所在處會立即調用,在錯誤檢查之前。當然還要注意一些常規的錯誤,比如 [`Py_BuildValue()`](../c-api/arg.xhtml#c.Py_BuildValue "Py_BuildValue") 可能會遭遇內存不足等等。 你還需要注意,用關鍵字參數調用 [`PyObject_Call()`](../c-api/object.xhtml#c.PyObject_Call "PyObject_Call") ,需要支持普通參數和關鍵字參數。有如如上例子中,我們使用 [`Py_BuildValue()`](../c-api/arg.xhtml#c.Py_BuildValue "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); ``` ## 1.7. 提取擴展函數的參數 函數 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 的聲明如下: ``` int PyArg_ParseTuple(PyObject *arg, const char *format, ...); ``` 參數 *arg* 必須是一個元組對象,包含從 Python 傳遞給 C 函數的參數列表。*format* 參數必須是一個格式字符串,語法請參考 Python C/API 手冊中的 [語句解釋及變量編譯](../c-api/arg.xhtml#arg-parsing)。剩余參數是各個變量的地址,類型要與格式字符串對應。 注意 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "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) */ } ``` ## 1.8. 給擴展函數的關鍵字參數 函數 [`PyArg_ParseTupleAndKeywords()`](../c-api/arg.xhtml#c.PyArg_ParseTupleAndKeywords "PyArg_ParseTupleAndKeywords") 聲明如下: ``` int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict, const char *format, char *kwlist[], ...); ``` 參數 *arg* 和 *format* 定義同 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 。參數 *kwdict* 是關鍵字字典,用于接受運行時傳來的關鍵字參數。參數 *kwlist* 是一個 *NULL* 結尾的字符串,定義了可以接受的參數名,并從左到右與 *format* 中各個變量對應。如果執行成功 [`PyArg_ParseTupleAndKeywords()`](../c-api/arg.xhtml#c.PyArg_ParseTupleAndKeywords "PyArg_ParseTupleAndKeywords") 會返回true,否則返回false并拋出異常。 注解 嵌套的元組在使用關鍵字參數時無法生效,不在 *kwlist* 中的關鍵字參數會導致 [`TypeError`](../library/exceptions.xhtml#TypeError "TypeError") 異常。 如下是使用關鍵字參數的例子模塊,作者是 Geoff Philbrick ([phibrick@hks.com](mailto:phibrick%40hks.com)): ``` #define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ #include <Python.h> static 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)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); } ``` ## 1.9. 構造任意值 這個函數與 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 很相似,聲明如下: ``` PyObject *Py_BuildValue(const char *format, ...); ``` 接受一個格式字符串,與 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 相同,但是參數必須是原變量的地址指針(輸入給函數,而非輸出)。最終返回一個Python對象適合于返回C函數調用給Python代碼。 一個與 [`PyArg_ParseTuple()`](../c-api/arg.xhtml#c.PyArg_ParseTuple "PyArg_ParseTuple") 的不同是,后面可能需要的要求返回一個元組(Python參數里誒包總是在內部描述為元組),比如用于傳遞給其他Python函數以參數。 [`Py_BuildValue()`](../c-api/arg.xhtml#c.Py_BuildValue "Py_BuildValue") 并不總是生成元組,在多于1個參數時會生成元組,而如果沒有參數則返回 `None` ,一個參數則直接返回該參數的對象。如果要求強制生成一個長度為空的元組,或包含一個元素的元組,需要在格式字符串中加上括號。 例子(左側是調用,右側是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)) ``` ## 1.10. 引用計數 在C/C++語言中,程序員負責動態分配和回收堆(heap)當中的內存。在C里,通過函數 `malloc()` 和 `free()` 來完成。在C++里是操作 `new` 和 `delete` 來實現相同的功能。 每個由 `malloc()` 分配的內存塊,最終都要由 `free()` 退回到可用內存池里面去。而調用 `free()` 的時機非常重要,如果一個內存塊忘了 `free()` 則會導致內存泄漏,這塊內存在程序結束前將無法重新使用。這叫做 *內存泄漏* 。而如果對同一內存塊 `free()` 了以后,另外一個指針再次訪問,則再次使用 `malloc()` 復用這塊內存會導致沖突。這叫做 *野指針* 。等同于使用未初始化的數據,core dump,錯誤結果,神秘的崩潰等。 內存泄露往往發生在一些并不常見的代碼流程上面。比如一個函數申請了內存以后,做了些計算,然后釋放內存塊。現在一些對函數的修改可能增加對計算的測試并檢測錯誤條件,然后過早的從函數返回了。這很容易忘記在退出前釋放內存,特別是后期修改的代碼。這種內存泄漏,一旦引入,通常很長時間都難以檢測到,錯誤退出被調用的頻度較低,而現代電腦又有非常巨大的虛擬內存,所以泄漏僅在長期運行或頻繁調用泄漏函數時才會變得明顯。因此,有必要避免內存泄漏,通過代碼規范會策略來最小化此類錯誤。 Python通過 `malloc()` 和 `free()` 包含大量的內存分配和釋放,同樣需要避免內存泄漏和野指針。他選擇的方法就是 *引用計數* 。其原理比較簡單:每個對象都包含一個計數器,計數器的增減與對象引用的增減直接相關,當引用計數為0時,表示對象已經沒有存在的意義了,對象就可以刪除了。 另一個叫法是 *自動垃圾回收* 。(有時引用計數也被看作是垃圾回收策略,于是這里的"自動"用以區分兩者)。自動垃圾回收的優點是用戶不需要明確的調用 `free()` 。(另一個優點是改善速度或內存使用,然而這并不難)。缺點是對C,沒有可移植的自動垃圾回收器,而引用計數則可以可移植的實現(只要 `malloc()` 和 `free()` 函數是可用的,這也是C標準擔保的)。也許以后有一天會出現可移植的自動垃圾回收器,但在此前我們必須與引用計數一起工作。 Python使用傳統的引用計數實現,也提供了循環監測器,用以檢測引用循環。這使得應用無需擔心直接或間接的創建了循環引用,這是引用計數垃圾收集的一個弱點。引用循環是對象(可能直接)的引用了本身,所以循環中的每個對象的引用計數都不是0。典型的引用計數實現無法回收處于引用循環中的對象,或者被循環所引用的對象,哪怕沒有循環以外的引用了。 循環探測器可以檢測垃圾循環并回收。 [`gc`](../library/gc.xhtml#module-gc "gc: Interface to the cycle-detecting garbage collector.") 模塊提供了方法運行探測器 ( [`collect()`](../library/gc.xhtml#gc.collect "gc.collect") 函數) ,而且可以在運行時配置禁用探測器。循環探測器被當作可選組件,默認是包含的,也可以在構建時禁用,在Unix平臺(包括Mac OS X)使用 `--without-cycle-gc` 選項到 **configure** 腳本。如果循環探測器被禁用, [`gc`](../library/gc.xhtml#module-gc "gc: Interface to the cycle-detecting garbage collector.") 模塊就不可用了。 ### 1.10.1. Python中的引用計數 有兩個宏 `Py_INCREF(x)` 和 `Py_DECREF(x)` ,會處理引用計數的增減。 [`Py_DECREF()`](../c-api/refcounting.xhtml#c.Py_DECREF "Py_DECREF") 也會在引用計數到達0時釋放對象。為了靈活,并不會直接調用 `free()` ,而是通過對象的 *類型對象* 的函數指針來調用。為了這個目的(或其他的),每個對象同時包含一個指向自身類型對象的指針。 最大的問題依舊:何時使用 `Py_INCREF(x)` 和 `Py_DECREF(x)` ?我們首先引入一些概念。沒有人"擁有"一個對象,你可以 *擁有一個引用* 到一個對象。一個對象的引用計數定義為擁有引用的數量。引用的擁有者有責任調用 [`Py_DECREF()`](../c-api/refcounting.xhtml#c.Py_DECREF "Py_DECREF") ,在引用不再需要時。引用的擁有關系可以被傳遞。有三種辦法來處置擁有的引用:傳遞、存儲、調用 [`Py_DECREF()`](../c-api/refcounting.xhtml#c.Py_DECREF "Py_DECREF") 。忘記處置一個擁有的引用會導致內存泄漏。 還可以 *借用* [2](#id6) 一個對象的引用。借用的引用不應該調用 [`Py_DECREF()`](../c-api/refcounting.xhtml#c.Py_DECREF "Py_DECREF") 。借用者必須確保不能持有對象超過擁有者借出的時間。在擁有者處置對象后使用借用的引用是有風險的,應該完全避免 [3](#id7) 。 借用相對于引用的優點是你無需擔心整條路徑上代碼的引用,或者說,通過借用你無需擔心內存泄漏的風險。借用的缺點是一些看起來正確代碼上的借用可能會在擁有者處置后使用對象。 借用可以變為擁有引用,通過調用 [`Py_INCREF()`](../c-api/refcounting.xhtml#c.Py_INCREF "Py_INCREF") 。這不會影響已經借出的擁有者的狀態。這回創建一個新的擁有引用,并給予完全的擁有者責任(新的擁有者必須恰當的處置引用,就像之前的擁有者那樣)。 ### 1.10.2. 擁有規則 當一個對象引用傳遞進出一個函數時,函數的接口應該指定擁有關系的傳遞是否包含引用。 大多數函數返回一個對象的引用,并傳遞引用擁有關系。通常,所有創建對象的函數,例如 [`PyLong_FromLong()`](../c-api/long.xhtml#c.PyLong_FromLong "PyLong_FromLong") 和 [`Py_BuildValue()`](../c-api/arg.xhtml#c.Py_BuildValue "Py_BuildValue") ,會傳遞擁有關系給接收者。即便是對象不是真正新的,你仍然可以獲得對象的新引用。一個實例是 [`PyLong_FromLong()`](../c-api/long.xhtml#c.PyLong_FromLong "PyLong_FromLong") 維護了一個流行值的緩存,并可以返回已緩存項目的新引用。 很多另一個對象提取對象的函數,也會傳遞引用關系,例如 [`PyObject_GetAttrString()`](../c-api/object.xhtml#c.PyObject_GetAttrString "PyObject_GetAttrString") 。這里的情況不夠清晰,一些不太常用的例程是例外的 [`PyTuple_GetItem()`](../c-api/tuple.xhtml#c.PyTuple_GetItem "PyTuple_GetItem") , [`PyList_GetItem()`](../c-api/list.xhtml#c.PyList_GetItem "PyList_GetItem") , [`PyDict_GetItem()`](../c-api/dict.xhtml#c.PyDict_GetItem "PyDict_GetItem") , [`PyDict_GetItemString()`](../c-api/dict.xhtml#c.PyDict_GetItemString "PyDict_GetItemString") 都是返回從元組、列表、字典里借用的引用。 函數 [`PyImport_AddModule()`](../c-api/import.xhtml#c.PyImport_AddModule "PyImport_AddModule") 也會返回借用的引用,哪怕可能會返回創建的對象:這個可能因為一個擁有的引用對象是存儲在 `sys.modules` 里。 當你傳遞一個對象引用到另一個函數時,通常函數是借用出去的。如果需要存儲,就使用 [`Py_INCREF()`](../c-api/refcounting.xhtml#c.Py_INCREF "Py_INCREF") 來變成獨立的擁有者。這個規則有兩個重要的例外: [`PyTuple_SetItem()`](../c-api/tuple.xhtml#c.PyTuple_SetItem "PyTuple_SetItem") 和 [`PyList_SetItem()`](../c-api/list.xhtml#c.PyList_SetItem "PyList_SetItem") 。這些函數接受傳遞來的引用關系,哪怕會失敗!(注意 [`PyDict_SetItem()`](../c-api/dict.xhtml#c.PyDict_SetItem "PyDict_SetItem") 及其同類不會接受引用關系,他們是"正常的")。 當一個C函數被Python調用時,會從調用方傳來的參數借用引用。調用者擁有對象的引用,所以借用的引用生命周期可以保證到函數返回。只要當借用的引用需要存儲或傳遞時,就必須轉換為擁有的引用,通過調用 [`Py_INCREF()`](../c-api/refcounting.xhtml#c.Py_INCREF "Py_INCREF") 。 Python調用從C函數返回的對象引用時必須是擁有的引用---擁有關系被從函數傳遞給調用者。 ### 1.10.3. 危險的薄冰 有少數情況下,借用的引用看起來無害,但卻可能導致問題。這通常是因為解釋器的隱式調用,并可能導致引用擁有者處置這個引用。 首先需要特別注意的情況是使用 [`Py_DECREF()`](../c-api/refcounting.xhtml#c.Py_DECREF "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]` ,然后替換 `list[1]` 為值 `0` ,最后打印借用的引用。看起來無害是吧,但卻不是。 我們跟著控制流進入 [`PyList_SetItem()`](../c-api/list.xhtml#c.PyList_SetItem "PyList_SetItem") 。列表擁有者引用了其所有成員,所以當成員1被替換時,就必須處置原來的成員1。現在假設原來的成員1是用戶定義類的實例,且假設這個類定義了 [`__del__()`](../reference/datamodel.xhtml#object.__del__ "object.__del__") 方法。如果這個類實例的引用計數是1,那么處置動作就會調用 [`__del__()`](../reference/datamodel.xhtml#object.__del__ "object.__del__") 方法。 既然是Python寫的, [`__del__()`](../reference/datamodel.xhtml#object.__del__ "object.__del__") 方法可以執行任意Python代碼。是否可能在 `bug()` 的 `item` 廢止引用呢,是的。假設列表傳遞到 `bug()` 會被 [`__del__()`](../reference/datamodel.xhtml#object.__del__ "object.__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__()`](../reference/datamodel.xhtml#object.__del__ "object.__del__") 方法會失敗。 這個問題的第二種情況是借用的引用涉及線程的變種。通常,Python解釋器里多個線程無法進入對方的路徑,因為有個全局鎖保護著Python整個對象空間。但可以使用宏 [`Py_BEGIN_ALLOW_THREADS`](../c-api/init.xhtml#c.Py_BEGIN_ALLOW_THREADS "Py_BEGIN_ALLOW_THREADS") 來臨時釋放這個鎖,重新獲取鎖用 [`Py_END_ALLOW_THREADS`](../c-api/init.xhtml#c.Py_END_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! */ } ``` ### 1.10.4. NULL指針 通常,函數接受對象引用作為參數,而非期待你傳入 *NULL* 指針,你非這么干會導致dump core (或者之后導致core dumps) 。函數返回對象引用時,返回的 *NULL* 用以指示發生了異常。 *NULL* 參數的理由在從其他函數接收時并未測試,如果每個函數都測試 *NULL* ,就會導致大量的冗余測試,并使得代碼運行更慢。 好的方法是僅在 "源頭" 測試 *NULL* ,當一個指針可能是 *NULL* 時,例如 `malloc()` 或者從一個可能拋出異常的函數。 宏 [`Py_INCREF()`](../c-api/refcounting.xhtml#c.Py_INCREF "Py_INCREF") 和 [`Py_DECREF()`](../c-api/refcounting.xhtml#c.Py_DECREF "Py_DECREF") 不會檢查 *NULL* 指針。但他們的變種 [`Py_XINCREF()`](../c-api/refcounting.xhtml#c.Py_XINCREF "Py_XINCREF") 和 [`Py_XDECREF()`](../c-api/refcounting.xhtml#c.Py_XDECREF "Py_XDECREF") 會檢查。 用以檢查對象類型的宏( `Pytype_Check()` )不會檢查 *NULL* 指針,有很多代碼會多次測試一個對象是否是預期的類型,這可能產生冗余的測試。而 *NULL* 檢查沒有冗余。 C函數調用機制會確保傳遞到C函數的參數列表 (例如 `args` )不會是 *NULL* ,實際上會確保總是元組 [4](#id8) 。 把 *NULL* 指針轉義給Python用戶是個嚴重的錯誤。 ## 1.11. 在C++中編寫擴展 還可以在C++中編寫擴展模塊,只是有些限制。如果主程序(Python解釋器)是使用C編譯器來編譯和鏈接的,全局或靜態對象的構造器就不能使用。而如果是C++編譯器來鏈接的就沒有這個問題。函數會被Python解釋器調用(通常就是模塊初始化函數)必須聲明為 `extern "C"` 。而是否在 `extern "C" {...}` 里包含Python頭文件則不是那么重要,因為如果定義了符號 `__cplusplus` 則已經是這么聲明的了(所有現代C++編譯器都會定義這個符號)。 ## 1.12. 給擴展模塊提供C API 很多擴展模塊提供了新的函數和類型供Python使用,但有時擴展模塊里的代碼也可以被其他擴展模塊使用。例如,一個擴展模塊可以實現一個類型 "collection" 看起來是沒有順序的。就像是Python列表類型,擁有C API允許擴展模塊來創建和維護列表,這個新的集合類型可以有一堆C函數用于給其他擴展模塊直接使用。 開始看起來很簡單:只需要編寫函數(無需聲明為 `static` ),提供恰當的頭文件,以及C API的文檔。實際上在所有擴展模塊都是靜態鏈接到Python解釋器時也是可以正常工作的。當模塊以共享庫鏈接時,一個模塊中的符號定義對另一個模塊不可見。可見的細節依賴于操作系統,一些系統的Python解釋器使用全局命名空間(例如Windows),有些則在鏈接時需要一個嚴格的已導入符號列表(一個例子是AIX),或者提供可選的不同策略(如Unix系列)。即便是符號是全局可見的,你要調用的模塊也可能尚未加載。 可移植性需要不能對符號可見性做任何假設。這意味著擴展模塊里的所有符號都應該聲明為 `static` ,除了模塊的初始化函數,來避免與其他擴展模塊的命名沖突(在段落 [模塊方法表和初始化函數](#methodtable) 中討論) 。這意味著符號應該 *必須* 通過其他導出方式來供其他擴展模塊訪問。 Python提供了一個特別的機制來傳遞C級別信息(指針),從一個擴展模塊到另一個:Capsules。一個Capsule是一個Python數據類型,會保存指針( `void *` )。Capsule只能通過其C API來創建和訪問,但可以像其他Python對象一樣的傳遞。通常,我們可以指定一個擴展模塊命名空間的名字。其他擴展模塊可以導入這個模塊,獲取這個名字的值,然后從Capsule獲取指針。 Capsule可以用多種方式導出C API給擴展模塊。每個函數可以用自己的Capsule,或者所有C API指針可以存儲在一個數組里,數組地址再發布給Capsule。存儲和獲取指針也可以用多種方式,供客戶端模塊使用。 使用的方法,對Capsule的名字很重要。函數 [`PyCapsule_New()`](../c-api/capsule.xhtml#c.PyCapsule_New "PyCapsule_New") 會接受一個名字參數( `const char *` ),你可以傳入 *NULL* 給名字,但強烈建議指定個名字。恰當的命名Capsule提供了一定程度的運行時類型安全;而卻沒有可行的方法來告知我們一個未命名的Capsule。 通常來說,Capsule用于暴露C API,其名字應該遵循如下規范: ``` modulename.attributename ``` 便利函數 [`PyCapsule_Import()`](../c-api/capsule.xhtml#c.PyCapsule_Import "PyCapsule_Import") 可以方便的載入通過Capsule提供的C API,僅在Capsule的名字匹配時。這個行為為C API用戶提供了高度的確定性來載入正確的C API。 如下例子展示了將大部分負擔交由導出模塊作者的方法,適用于常用的庫模塊。其會存儲所有C API指針(例子里只有一個)在 `void` 指針的數組里,并使其值變為Capsule。對應的模塊頭文件提供了宏來管理導入模塊和獲取C API指針;客戶端模塊只需要在訪問C API前調用這個宏即可。 導出的模塊修改自 `spam` 模塊,來自 [一個簡單的例子](#extending-simpleexample) 段落。函數 `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) */ ``` 客戶端模塊必須在其初始化函數里按順序調用函數 `import_spam()` (或其他宏)才能訪問函數 `PySpam_System()` 。 ``` 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` 過于復雜。當然,對每個要導出的函數,基本結構是相似的,所以只需要學習一次。 最后需要提醒的是Capsule提供了額外的功能,用于存儲在Capsule里的指針的內存分配和釋放。細節參考 Python/C API參考手冊的章節 [膠囊](../c-api/capsule.xhtml#capsules) 和Capsule的實現(在Python源碼發行包的 `Include/pycapsule.h` 和 `Objects/pycapsule.c` )。 腳注 [1](#id1)這個函數的接口已經在標準模塊 [`os`](../library/os.xhtml#module-os "os: Miscellaneous operating system interfaces.") 里了,這里作為一個簡單而直接的例子。 [2](#id2)術語"借用"一個引用是不完全正確的:擁有者仍然有引用的拷貝。 [3](#id3)檢查引用計數至少為1 **沒有用** ,引用計數本身可以在已經釋放的內存里,并有可能被其他對象所用。 [4](#id4)當你使用 "舊式" 風格調用約定時,這些保證不成立,盡管這依舊存在于很多舊代碼中。 ### 導航 - [索引](../genindex.xhtml "總目錄") - [模塊](../py-modindex.xhtml "Python 模塊索引") | - [下一頁](newtypes_tutorial.xhtml "2. 自定義擴展類型:教程") | - [上一頁](index.xhtml "擴展和嵌入 Python 解釋器") | - ![](https://box.kancloud.cn/a721fc7ec672275e257bbbfde49a4d4e_16x16.png) - [Python](https://www.python.org/) ? - zh\_CN 3.7.3 [文檔](../index.xhtml) ? - [擴展和嵌入 Python 解釋器](index.xhtml) ? - $('.inline-search').show(0); | ? [版權所有](../copyright.xhtml) 2001-2019, Python Software Foundation. Python 軟件基金會是一個非盈利組織。 [請捐助。](https://www.python.org/psf/donations/) 最后更新于 5月 21, 2019. [發現了問題](../bugs.xhtml)? 使用[Sphinx](http://sphinx.pocoo.org/)1.8.4 創建。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看