擴充套件/嵌入常見問題

我可以用 C 語言建立自己的函式嗎?

是的,您可以在 C 語言中建立包含函式、變數、異常甚至新型別的內建模組。這在文件擴充套件和嵌入 Python 直譯器中有所解釋。

大多數中高階 Python 書籍也會涵蓋這個主題。

我可以用 C++ 語言建立自己的函式嗎?

是的,使用 C++ 中發現的 C 相容性特性。將 extern "C" { ... } 放在 Python 標頭檔案周圍,並在將由 Python 直譯器呼叫的每個函式前放置 extern "C"。帶有建構函式的全域性或靜態 C++ 物件可能不是一個好主意。

編寫 C 語言很難;有什麼替代方案嗎?

根據您嘗試執行的操作,有許多替代方案可以編寫自己的 C 擴充套件。推薦的第三方工具提供了更簡單和更復雜的方法來為 Python 建立 C 和 C++ 擴充套件。

我如何從 C 語言執行任意 Python 語句?

執行此操作的最高階函式是 PyRun_SimpleString(),它接受一個字串引數,該引數在模組 __main__ 的上下文中執行,成功返回 0,發生異常(包括 SyntaxError)時返回 -1。如果您想要更多控制,請使用 PyRun_String();請參閱 Python/pythonrun.cPyRun_SimpleString() 的原始碼。

我如何從 C 語言評估任意 Python 表示式?

呼叫上一個問題中的函式 PyRun_String(),使用起始符號 Py_eval_input;它會解析表示式,評估它並返回其值。

如何從 Python 物件中提取 C 值?

這取決於物件的型別。如果它是一個元組,PyTuple_Size() 返回其長度,PyTuple_GetItem() 返回指定索引處的項。列表具有類似的函式,PyList_Size()PyList_GetItem()

對於位元組,PyBytes_Size() 返回其長度,PyBytes_AsStringAndSize() 提供指向其值和長度的指標。請注意,Python 位元組物件可能包含空位元組,因此不應使用 C 的 strlen()

要測試物件的型別,首先確保它不是 NULL,然後使用 PyBytes_Check()PyTuple_Check()PyList_Check() 等。

還有一個針對 Python 物件的高階 API,由所謂的“抽象”介面提供——有關詳細資訊,請閱讀 Include/abstract.h。它允許使用 PySequence_Length()PySequence_GetItem() 等呼叫以及許多其他有用的協議(例如數字 (PyNumber_Index() 等) 和 PyMapping API 中的對映)與任何型別的 Python 序列進行介面。

如何使用 Py_BuildValue() 建立任意長度的元組?

你不能。請改用 PyTuple_Pack()

如何從 C 語言呼叫物件的方法?

函式 PyObject_CallMethod() 可用於呼叫物件的任意方法。引數是物件、要呼叫的方法名稱、類似於 Py_BuildValue() 中使用的格式字串以及引數值。

PyObject *
PyObject_CallMethod(PyObject *object, const char *method_name,
                    const char *arg_format, ...);

這適用於任何具有方法的物件——無論是內建的還是使用者定義的。您有責任最終 Py_DECREF() 返回值。

例如,要使用引數 10, 0 呼叫檔案物件的“seek”方法(假設檔案物件指標是“f”)

res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
if (res == NULL) {
        ... an exception occurred ...
}
else {
        Py_DECREF(res);
}

請注意,由於 PyObject_CallObject() 總是 需要一個元組作為引數列表,要呼叫沒有引數的函式,請將格式設定為“()”,要呼叫帶有一個引數的函式,請將引數括在括號中,例如“(i)”。

如何捕獲 PyErr_Print()(或任何列印到 stdout/stderr 的內容)的輸出?

在 Python 程式碼中,定義一個支援 write() 方法的物件。將此物件分配給 sys.stdoutsys.stderr。呼叫 print_error,或直接讓標準回溯機制工作。然後,輸出將傳送到您的 write() 方法傳送的地方。

最簡單的方法是使用 io.StringIO

>>> import io, sys
>>> sys.stdout = io.StringIO()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(sys.stdout.getvalue())
foo
hello world!

執行相同操作的自定義物件將如下所示

>>> import io, sys
>>> class StdoutCatcher(io.TextIOBase):
...     def __init__(self):
...         self.data = []
...     def write(self, stuff):
...         self.data.append(stuff)
...
>>> import sys
>>> sys.stdout = StdoutCatcher()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(''.join(sys.stdout.data))
foo
hello world!

如何從 C 語言訪問用 Python 編寫的模組?

您可以按如下方式獲取模組物件的指標

module = PyImport_ImportModule("<modulename>");

如果模組尚未匯入(即它尚不存在於 sys.modules 中),它會初始化模組;否則它只會返回 sys.modules["<modulename>"] 的值。請注意,它不會將模組輸入到任何名稱空間中——它只確保模組已初始化並存儲在 sys.modules 中。

然後,您可以按如下方式訪問模組的屬性(即模組中定義的任何名稱)

attr = PyObject_GetAttrString(module, "<attrname>");

呼叫 PyObject_SetAttrString() 為模組中的變數賦值也有效。

如何從 Python 語言與 C++ 物件進行介面?

根據您的要求,有多種方法。要手動執行此操作,請首先閱讀“擴充套件和嵌入”文件。請注意,對於 Python 執行時系統,C 和 C++ 之間沒有太大區別——因此,圍繞 C 結構(指標)型別構建新的 Python 型別的策略也適用於 C++ 物件。

對於 C++ 庫,請參閱編寫 C 語言很難;有什麼替代方案嗎?

我使用 Setup 檔案添加了一個模組,但 make 失敗了;為什麼?

Setup 必須以換行符結尾,如果沒有換行符,構建過程就會失敗。(修復這個問題需要一些醜陋的 shell 指令碼 hack 技巧,而且這個 bug 太小,不值得付出努力。)

如何除錯擴充套件?

當使用 GDB 除錯動態載入的擴充套件時,在載入擴充套件之前無法在擴充套件中設定斷點。

在您的 .gdbinit 檔案(或互動式)中,新增命令

br _PyImport_LoadDynamicModule

然後,當您執行 GDB 時

$ gdb /local/bin/python
gdb) run myscript.py
gdb) continue # repeat until your extension is loaded
gdb) finish   # so that your extension is loaded
gdb) br myfunction.c:50
gdb) continue

我想在我的 Linux 系統上編譯一個 Python 模組,但缺少一些檔案。為什麼?

大多數打包版本的 Python 省略了一些編譯 Python 擴充套件所需的檔案。

對於 Red Hat,安裝 python3-devel RPM 以獲取必要的檔案。

對於 Debian,執行 apt-get install python3-dev

如何區分“不完整輸入”和“無效輸入”?

有時您想模仿 Python 互動式直譯器的行為,當輸入不完整時(例如,您輸入了“if”語句的開頭或者您沒有關閉括號或三引號字串),它會給您一個繼續提示,但當輸入無效時,它會立即給您一個語法錯誤訊息。

在 Python 中,您可以使用 codeop 模組,它足以近似解析器的行為。例如,IDLE 就使用了它。

在 C 語言中,最簡單的方法是呼叫 PyRun_InteractiveLoop()(可能在單獨的執行緒中),讓 Python 直譯器為您處理輸入。您還可以將 PyOS_ReadlineFunctionPointer() 設定為指向您的自定義輸入函式。有關更多提示,請參閱 Modules/readline.cParser/myreadline.c

如何找到未定義的 g++ 符號 __builtin_new 或 __pure_virtual?

要動態載入 g++ 擴充套件模組,您必須重新編譯 Python,使用 g++ 重新連結它(更改 Python Modules Makefile 中的 LINKCC),並使用 g++ 連結您的擴充套件模組(例如,g++ -shared -o mymodule.so mymodule.o)。

我能否建立一個物件類,其中一些方法用 C 語言實現,另一些用 Python 語言實現(例如透過繼承)?

是的,您可以繼承內建類,例如 intlistdict 等。

Boost Python 庫 (BPL, https://www.boost.org/libs/python/doc/index.html) 提供了一種從 C++ 執行此操作的方法(即,您可以使用 BPL 繼承用 C++ 編寫的擴充套件類)。