擴充套件/嵌入常見問題¶
我可以用 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.c
中 PyRun_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.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 指令碼 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.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 語言實現(例如透過繼承)?¶
是的,您可以繼承內建類,例如 int
、list
、dict
等。
Boost Python 庫 (BPL, https://www.boost.org/libs/python/doc/index.html) 提供了一種從 C++ 執行此操作的方法(即,您可以使用 BPL 繼承用 C++ 編寫的擴充套件類)。