擴展/嵌入常見問題?

可以使用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 等工具包裝庫的數據類型和函數。 SIPCXX 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 中的各種映射等等。

如何從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.stdoutsys.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.cParser/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中實現(例如通過繼承)??

是的,您可以繼承內置類,例如 intlistdict 等。

Boost Python庫(BPL,http://www.boost.org/libs/python/doc/index.html)提供了一種從C ++執行此操作的方法(即,您可以使用BPL繼承自C ++編寫的擴展類 )。