扩展/嵌入常见问题解答

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

是的,你可以在 C 中创建包含函数、变量、异常甚至新类型的内置模块。这在文档 扩展和嵌入 Python 解释器 中进行了说明。

大多数中级或高级 Python 书籍也会涵盖此主题。

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

是的,使用 C++ 中的 C 兼容性功能。将 extern "C" { ... } 放在 Python 包含文件的周围,并在每个将由 Python 解释器调用的函数前放置 extern "C"。带有构造函数的全局或静态 C++ 对象可能不是一个好主意。

编写 C 很困难;还有其他选择吗?

根据你想要做什么,编写自己的 C 扩展有多种选择。

Cython 及其相关 Pyrex 是编译器,它们接受略微修改形式的 Python 并生成相应的 C 代码。Cython 和 Pyrex 使得无需了解 Python 的 C API 即可编写扩展。

如果你需要与某些 C 或 C++ 库进行交互,而目前尚不存在 Python 扩展,你可以尝试使用 SWIG 等工具包装库的数据类型和函数。 SIPCXX BoostWeave 也是包装 C++ 库的替代方案。

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

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

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

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

如何从 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() 等调用与任何类型的 Python 序列进行交互,以及许多其他有用的协议,例如数字(PyNumber_Index() 等)和 PyMapping API 中的映射。

如何使用 Py_BuildValue() 创建任意长度的元组?

不能。请改用 PyTuple_Pack()

如何从 C 调用对象的 method?

PyObject_CallMethod() 函数可用于调用对象的任意 method。参数是对象、要调用的 method 的名称、一个格式字符串(与 Py_BuildValue() 一起使用)以及参数值

PyObject *
PyObject_CallMethod(PyObject *object, const char *method_name,
                    const char *arg_format, ...);

这适用于任何具有 method 的对象——无论是内置的还是用户定义的。您负责最终 Py_DECREF()“ing 返回值。

例如,调用文件对象的“seek”method,参数为 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() method 的对象。将此对象分配给 sys.stdoutsys.stderr。调用 print_error,或仅允许标准回溯机制工作。然后,输出将转到您的 write() method 发送到的任何位置。

执行此操作的最简单方法是使用 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 失败了;为什么?

如果 Setup 没有以换行符结尾,则构建过程将失败。(修复此问题需要一些丑陋的 shell 脚本 hackery,而且此错误非常小,似乎不值得付出努力。)

如何调试扩展?

在将动态加载的扩展与 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

如何查找未定义的 g++ 符号 __builtin_new 或 __pure_virtual?

要动态加载 g++ 扩展模块,您必须重新编译 Python,使用 g++ 重新链接它(更改 Python 模块 Makefile 中的 LINKCC),并使用 g++ 链接扩展模块(例如,g++ -shared -o mymodule.so mymodule.o)。

我能否创建一个对象类,其中一些方法在 C 中实现,而另一些方法在 Python 中实现(例如,通过继承)?

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

Boost Python 库 (BPL, https://boost.ac.cn/libs/python/doc/index.html) 提供了一种从 C++ 中执行此操作的方法(即,您可以使用 BPL 继承自用 C++ 编写的扩展类)。