擴充/嵌入常見問題集¶
我可以在 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, dependingon what you're trying to do.Recommended third party toolsoffer 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,constchar*method_name,constchar*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 類別:
>>>importio,sys>>>sys.stdout=io.StringIO()>>>print('foo')>>>print('hello world!')>>>sys.stderr.write(sys.stdout.getvalue())foohello world!
執行相同操作的自定義物件如下所示:
>>>importio,sys>>>classStdoutCatcher(io.TextIOBase):...def__init__(self):...self.data=[]...defwrite(self,stuff):...self.data.append(stuff)...>>>importsys>>>sys.stdout=StdoutCatcher()>>>print('foo')>>>print('hello world!')>>>sys.stderr.write(''.join(sys.stdout.data))foohello 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/pythongdb) run myscript.pygdb) continue # repeat until your extension is loadedgdb) finish # so that your extension is loadedgdb) br myfunction.c:50gdb) continue
我想在我的 Linux 系統上編譯一個 Python 模組,但是缺少一些檔案。為什麼?¶
大多數打包版本的 Python 省略了編譯 Python 擴充所需的一些檔案。
在 Red Hat 上,請安裝 python3-devel RPM 來取得必要的檔案。
對於 Debian,運行apt-getinstallpython3-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-omymodule.somymodule.o)。
我可以用一些用 C 實作的方法和用 Python 實作的其他方法(例如透過繼承)建立一個物件類別嗎?¶
是的,你可以繼承內建類別,例如int、list、dict 等。
Boost Python 函式庫(BPL,https://www.boost.org/libs/python/doc/index.html)提供了一種從 C++ 執行此操作的方法(即你可以使用 BPL 來繼承用 C++ 編寫的擴充類別)。