擴充/嵌入常見問題集¶
我可以在 C 中建立自己的函式嗎?¶
是的,你可以在 C 中建立包含函式、變數、例外甚至新型別的內建模組,擴充和嵌入 Python 直譯器 文件中有相關說明。
大多數中級或進階 Python 書籍也會涵蓋這個主題。
我可以在 C++ 中建立自己的函式嗎?¶
是的,可使用 C++ 中的 C 相容性功能。將 extern "C" { ... }
放在 Python 引入檔案周圍,並將 extern "C"
放在每個將由 Python 直譯器呼叫的函式之前。但具有構造函式的全域或靜態 C++ 物件可能不是一個好主意。
寫 C 很難;還有其他選擇嗎?¶
There are a number of alternatives to writing your own C extensions, depending on what you're trying to do. Recommended third party tools offer both simpler and more sophisticated approaches to creating C and C++ extensions for Python.
如何從 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) {
... 發生一個例外 ...
}
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 run-time 系統,C 和 C++ 之間並沒有太多區別 —— 因此圍繞 C 結構(指標)型別來構建新 Python 型別的策略也適用於 C++ 物件。
對於 C++ 函式庫,請參閱 寫 C 很難;還有其他選擇嗎?。
我使用安裝檔案新增了一個模組,但 make 失敗了;為什麼?¶
安裝程式必須以換行符結尾,如果那裡沒有換行符,構建過程將失敗。(解決這個問題需要一些醜陋的 shell 腳本 hackery,而且這個錯誤很小,似乎不值得付出努力。)
如何為擴充套件除錯?¶
將 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
模組,它充分模擬了剖析器 (parser) 的行為。像是 IDLE 就有使用它。
在 C 中執行此操作的最簡單方法是呼叫 PyRun_InteractiveLoop()
(可能是在單獨的執行緒中)並讓 Python 直譯器為你處理輸入。你還可以將 PyOS_ReadlineFunctionPointer()
設定為指向你的自定義輸入函式。有關更多提示,請參閱 Modules/readline.c
和 Parser/myreadline.c
。
如何找到未定義的 g++ 符號 __builtin_new 或 __pure_virtual?¶
要動態載入 g++ 擴充模組,你必須重新編譯 Python,並使用 g++ 重新鏈接它(更改 Python 模組 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++ 編寫的擴充類別)。