扩展/嵌入常见问题

我可以用 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 的情况下编写扩展。

如果您需要与当前不存在 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() 返回指定索引处的项。列表具有类似的功能,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 语言调用对象的方法?

可以使用 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_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 失败了;为什么?

Setup 文件必须以换行符结尾,如果没有换行符,则构建过程将失败。(修复此问题需要一些丑陋的 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 省略了一些编译 Python 扩展所需的文件。

对于 Red Hat,安装 python3-devel RPM 以获取必要的文件。

对于 Debian,运行 apt-get install python3-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 Modules Makefile 中的 LINKCC),并使用 g++ 链接您的扩展模块(例如,g++ -shared -o mymodule.so mymodule.o)。

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

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

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