擴充套件/嵌入常見問題¶
我可以用 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 之類的工具包裝該庫的資料型別和函式。SIP、CXX Boost 或 Weave 也是包裝 C++ 庫的替代方案。
我如何從 C 語言執行任意的 Python 語句?¶
執行此操作的最高級別函式是 PyRun_SimpleString()
,它接受一個要在 __main__
模組的上下文中執行的字串引數,成功時返回 0
,發生異常時返回 -1
(包括 SyntaxError
)。如果您想要更多控制,請使用 PyRun_String()
;請參閱 Python/pythonrun.c
中 PyRun_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.stdout
和 sys.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.c
和Parser/myreadline.c
。
我如何找到未定義的 g++ 符號 __builtin_new 或 __pure_virtual?¶
要動態載入 g++ 擴充套件模組,您必須重新編譯 Python,使用 g++ 重新連結它(更改 Python Modules Makefile 中的 LINKCC),並使用 g++ 連結您的擴充套件模組(例如,g++ -shared -o mymodule.so mymodule.o
)。
我是否可以建立一個物件類,其中一些方法在 C 中實現,而另一些方法在 Python 中實現(例如透過繼承)?¶
Boost Python Library (BPL, https://www.boost.org/libs/python/doc/index.html) 提供了一種從 C++ 執行此操作的方法(即,您可以使用 BPL 從 C++ 編寫的擴充套件類繼承)。