擴充套件/嵌入常見問題

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

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

大多數中級或高階 Python 書籍也會涵蓋此主題。

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

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

編寫 C 程式碼很難;有什麼替代方案嗎?

根據您要執行的操作,有很多替代方案可以用來編寫自己的 C 擴充套件。

Cython 及其相關 Pyrex 是編譯器,它們接受稍微修改的 Python 形式並生成相應的 C 程式碼。Cython 和 Pyrex 使您可以在無需學習 Python 的 C API 的情況下編寫擴充套件。

如果您需要與當前不存在 Python 擴充套件的某些 C 或 C++ 庫進行介面,您可以嘗試使用諸如 SWIG 之類的工具包裝該庫的資料型別和函式。SIPCXX BoostWeave 也是包裝 C++ 庫的替代方案。

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

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

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

從上一個問題呼叫帶有起始符號 Py_eval_input 的函式 PyRun_String();它會解析表示式,對其求值並返回其值。

我如何從 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() 等呼叫與任何型別的 Python 序列進行介面,以及許多其他有用的協議,例如數字(PyNumber_Index() 等)和 PyMapping API 中的對映。

如何使用 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 指令碼技巧,並且這個 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 Library (BPL, https://www.boost.org/libs/python/doc/index.html) 提供了一種從 C++ 執行此操作的方法(即,您可以使用 BPL 從 C++ 編寫的擴充套件類繼承)。