擴展/嵌入常見問題?
目錄
可以使用C語言中創建自己的函數嗎??
是的,您可以在C中創建包含函數、變量、異常甚至新類型的內置模塊。在文檔 擴展和嵌入 Python 解釋器 中有說明。
大多數中級或高級的Python書籍也涵蓋這個主題。
可以使用C++語言中創建自己的函數嗎??
是的,可以使用C ++中兼容C的功能。 在Python include文件周圍放置` extern“C”{...}` ,并在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 而在發生異常 (包括 SyntaxError) 時返回 -1。 如果你想要更多可控性,可以使用 PyRun_String();請在 Python/pythonrun.c 中查看 PyRun_SimpleString() 的源碼。
如何在 C 中對任意 Python 表達式求值??
可以調用前一問題中介紹的函數 PyRun_String() 并附帶起始標記符 Py_eval_input;它會解析表達式,對其求值并返回結果值。
如何從Python對象中提取C的值??
這取決于對象的類型。 如果是元組,PyTuple_Size() 可返回其長度而 PyTuple_GetItem() 可返回指定序號上的項。 對于列表也有類似的函數 PyListSize() 和 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() 處理。
例如調用某個文件對象的 "seek" 方法并傳入參數 10, 0 (假定文件對象的指針為 "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失敗了??
安裝程序必須以換行符結束,如果沒有換行符,則構建過程將失敗。 (修復這個需要一些丑陋的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版本不包含 /usr/lib/python2.x/config/ 目錄,該目錄中包含編譯Python擴展所需的各種文件。
對于Red Hat,安裝python-devel RPM以獲取必要的文件。
對于Debian,運行 apt-get install python-dev 。
如何區分“輸入不完整”和“輸入無效”??
有時,希望模仿Python交互式解釋器的行為,在輸入不完整時(例如,您鍵入了“if”語句的開頭,或者沒有關閉括號或三個字符串引號),給出一個延續提示,但當輸入無效時,立即給出一條語法錯誤消息。
在Python中,您可以使用 codeop 模塊,該模塊非常接近解析器的行為。例如,IDLE就使用了這個。
在C中執行此操作的最簡單方法是調用 PyRun_InteractiveLoop() (可能在單獨的線程中)并讓Python解釋器為您處理輸入。您還可以設置 PyOS_ReadlineFunctionPointer() 指向您的自定義輸入函數。有關更多提示,請參閱 Modules/readline.c 和 Parser/myreadline.c 。
但是,有時必須在與其他應用程序相同的線程中運行嵌入式Python解釋器,并且不能允許 PyRun_InteractiveLoop() 在等待用戶輸入時停止。那么另一個解決方案是調用 PyParser_ParseString() 并測試 e.error 等于 E_EOF ,如果等于,就意味著輸入不完整。這是一個示例代碼片段,未經測試,靈感來自Alex Farber的代碼:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <node.h>
#include <errcode.h>
#include <grammar.h>
#include <parsetok.h>
#include <compile.h>
int testcomplete(char *code)
/* code should end in \n */
/* return -1 for error, 0 for incomplete, 1 for complete */
{
node *n;
perrdetail e;
n = PyParser_ParseString(code, &_PyParser_Grammar,
Py_file_input, &e);
if (n == NULL) {
if (e.error == E_EOF)
return 0;
return -1;
}
PyNode_Free(n);
return 1;
}
另一個解決方案是嘗試使用 Py_CompileString() 編譯接收到的字符串。如果編譯時沒有出現錯誤,請嘗試通過調用 PyEval_EvalCode() 來執行返回的代碼對象。否則,請將輸入保存到以后。如果編譯失敗,找出是錯誤還是只需要更多的輸入-從異常元組中提取消息字符串,并將其與字符串 “分析時意外的EOF” 進行比較。下面是使用GNUreadline庫的完整示例(您可能希望在調用readline()時忽略 SIGINT ):
#include <stdio.h>
#include <readline.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <object.h>
#include <compile.h>
#include <eval.h>
int main (int argc, char* argv[])
{
int i, j, done = 0; /* lengths of line, code */
char ps1[] = ">>> ";
char ps2[] = "... ";
char *prompt = ps1;
char *msg, *line, *code = NULL;
PyObject *src, *glb, *loc;
PyObject *exc, *val, *trb, *obj, *dum;
Py_Initialize ();
loc = PyDict_New ();
glb = PyDict_New ();
PyDict_SetItemString (glb, "__builtins__", PyEval_GetBuiltins ());
while (!done)
{
line = readline (prompt);
if (NULL == line) /* Ctrl-D pressed */
{
done = 1;
}
else
{
i = strlen (line);
if (i > 0)
add_history (line); /* save non-empty lines */
if (NULL == code) /* nothing in code yet */
j = 0;
else
j = strlen (code);
code = realloc (code, i + j + 2);
if (NULL == code) /* out of memory */
exit (1);
if (0 == j) /* code was empty, so */
code[0] = '\0'; /* keep strncat happy */
strncat (code, line, i); /* append line to code */
code[i + j] = '\n'; /* append '\n' to code */
code[i + j + 1] = '\0';
src = Py_CompileString (code, "<stdin>", Py_single_input);
if (NULL != src) /* compiled just fine - */
{
if (ps1 == prompt || /* ">>> " or */
'\n' == code[i + j - 1]) /* "... " and double '\n' */
{ /* so execute it */
dum = PyEval_EvalCode (src, glb, loc);
Py_XDECREF (dum);
Py_XDECREF (src);
free (code);
code = NULL;
if (PyErr_Occurred ())
PyErr_Print ();
prompt = ps1;
}
} /* syntax error or E_EOF? */
else if (PyErr_ExceptionMatches (PyExc_SyntaxError))
{
PyErr_Fetch (&exc, &val, &trb); /* clears exception! */
if (PyArg_ParseTuple (val, "sO", &msg, &obj) &&
!strcmp (msg, "unexpected EOF while parsing")) /* E_EOF */
{
Py_XDECREF (exc);
Py_XDECREF (val);
Py_XDECREF (trb);
prompt = ps2;
}
else /* some other syntax error */
{
PyErr_Restore (exc, val, trb);
PyErr_Print ();
free (code);
code = NULL;
prompt = ps1;
}
}
else /* some non-syntax error */
{
PyErr_Print ();
free (code);
code = NULL;
prompt = ps1;
}
free (line);
}
}
Py_XDECREF(glb);
Py_XDECREF(loc);
Py_Finalize();
exit(0);
}
如何找到未定義的g++符號__builtin_new或__pure_virtual??
要動態加載g ++擴展模塊,必須重新編譯Python,要使用g ++重新鏈接(在Python Modules Makefile中更改LINKCC),及鏈接擴展模塊(例如: g++ -shared -o mymodule.so mymodule.o )。
能否創建一個對象類,其中部分方法在C中實現,而其他方法在Python中實現(例如通過繼承)??
是的,您可以繼承內置類,例如 int , list , dict 等。
Boost Python庫(BPL,http://www.boost.org/libs/python/doc/index.html)提供了一種從C ++執行此操作的方法(即,您可以使用BPL繼承自C ++編寫的擴展類 )。
