扩展/嵌入常见问题解答

我可以在C中创建自己的函数吗?

是的,您可以在C中创建包含函数、变量、异常甚至新类型的内置模块。 扩展和嵌入python解释器 .

大多数中级或高级的Python书籍也将涵盖这个主题。

我能在C++中创建自己的函数吗?

是的,使用C++中发现的C兼容性功能。地点 extern "C" {{ ... }} 在python周围包括文件和 extern "C" 在Python解释器将要调用的每个函数之前。使用构造函数的全局或静态C++对象可能不是一个好主意。

写C程序很难,还有别的选择吗?

有很多种方法可以替代编写自己的C扩展,这取决于您要做什么。

Cython 及其相关的 Pyrex 是接受稍微修改过的python形式并生成相应C代码的编译器。Cython和Pyrex使编写扩展成为可能,而无需学习Python的C API。

如果需要连接到一些没有Python扩展的C或C++库,那么可以尝试用一个工具来封装库的数据类型和函数,例如 SWIG . SIP _, CXX BoostWeave 也是封装C++库的替代方案。

如何从C执行任意的 Python 语句?

执行此操作的最高级别功能是 PyRun_SimpleString() 它需要在模块上下文中执行单个字符串参数 __main__ 回报 0 为了成功和 -1 发生异常时(包括 SyntaxError )如果你想要更多的控制,使用 PyRun_String() ;查看源 PyRun_SimpleString() 在里面 Python/pythonrun.c .

如何从C计算任意的 Python 表达式?

调用函数 PyRun_String() 从上一个带有开始符号的问题开始 Py_eval_input ;它解析表达式,计算表达式并返回其值。

如何从Python对象中提取C值?

这取决于对象的类型。如果是一个元组, PyTuple_Size() 返回其长度和 PyTuple_GetItem() 返回指定索引处的项。列表具有类似的功能, PyListSize()PyList_GetItem() .

对于字节, PyBytes_Size() 返回其长度和 PyBytes_AsStringAndSize() 提供指向其值及其长度的指针。注意,python bytes对象可能包含空字节,因此 strlen() 不应使用。

要测试对象的类型,首先确保它不是 NULL 然后使用 PyBytes_Check()PyTuple_Check()PyList_Check() 等。

还有一个到python对象的高级API,由所谓的“abstract”接口--read提供 Include/abstract.h 更多详情。它允许使用诸如 PySequence_Length()PySequence_GetItem() 以及许多其他有用的协议,如数字 (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() '返回值。

要调用,例如,带有参数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.stdoutsys.stderr . 调用print_错误,或者只允许标准的回溯机制工作。然后,输出将转到 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++对象?

根据您的需求,有许多方法。要手动执行此操作,请从阅读开始 the "Extending and Embedding" document . 对于Python运行时系统来说,C和C++之间没有太多的差异,因此,围绕C结构(指针)类型构建一个新的Python类型的策略也适用于C++对象。

对于C++库,请参见 写C程序很难,还有别的选择吗? .

我使用安装文件添加了一个模块,但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扩展所需的各种文件。

对于RedHat,安装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 更多提示。

但是,有时必须在与REST应用程序相同的线程中运行嵌入的Python解释器,并且不能允许 PyRun_InteractiveLoop() 在等待用户输入时停止。唯一的解决办法就是 PyParser_ParseString() 测试 e.error 等于 E_EOF ,表示输入不完整。以下是一个未经测试的示例代码片段,灵感来自亚历克斯·法伯的代码:

#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”进行比较,找出它是错误还是只需要更多的输入。下面是使用GNU readline库的完整示例(您可能希望忽略 SIGINT 调用readline())时:

#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++符号u builtin_new或u pure_virtual?

要动态加载g++扩展模块,必须重新编译python,使用g++重新链接它(在python modules makefile中更改linkcc),并使用g++链接扩展模块(例如, g++ -shared -o mymodule.so mymodule.o

我可以用一些在C中实现的方法和其他在Python中实现的方法(例如通过继承)创建一个对象类吗?

是的,可以从内置类继承,例如 intlistdict 等。

Boost Python库(BPL、HTTP://www. BOST.OR/LBS/PythON/DOCT/NETX.HTML)提供了一种从C++中实现这一点的方法(即,您可以从BC++中使用C++编写的扩展类继承)。