扩展/嵌入常见问题解答 — Python 文档

来自菜鸟教程
Python/docs/3.9/faq/extending
跳转至:导航、​搜索

扩展/嵌入常见问题

我可以在 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 之类的工具包装库的数据类型和函数。 SIPCXX BoostWeave 也是包装 C++ 库的替代方法。


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

执行此操作的最高级别函数是 PyRun_SimpleString(),它需要在模块 __main__ 的上下文中执行单个字符串参数,并返回 0 以表示成功和-1 发生异常时(包括 SyntaxError)。 如果你想要更多的控制,使用 PyRun_String(); 请参阅 Python/pythonrun.cPyRun_SimpleString() 的源代码。


如何从 C 评估任意 Python 表达式?

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


如何从 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() always 需要参数列表的元组,调用没有参数的函数,传递“()”作为格式,并调用带有一个的函数参数,将参数括在括号中,例如 “(一世)”。


如何捕获 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 脚本黑客,而且这个错误很小,似乎不值得付出努力。)


如何调试扩展?

将 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”进行比较,找出是错误还是需要更多输入。 这是一个使用 GNU readline 库的完整示例(您可能希望在调用 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++ 链接您的扩展模块(例如,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++ 编写的扩展类继承)。