扩展/嵌入常见问题

可以使用 C 语言创建自己的函数吗?

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

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

可以使用 C++ 语言创建自己的函数吗?

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

C 很难写,有没有其他选择?

有多个替代选择可用来编写你自己的 C 扩展,具体取决于你想要做什么。 推荐的第三方工具 提供了更简单或更复杂的方式来为 Python 创建 C 和 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() 将返回指定索引号上的项。对于列表也有类似的函数 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() 处理。

例如调用某个文件对象的 "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.stdout 和 sys.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 脚本操作,而且这个缺陷很小,看起来不值得花这么大力气。)

如何调试扩展?

When using GDB with dynamically loaded extensions, enable pending breakpoints to set a breakpoint before the extension is loaded. For example, to stop in a function named myfunction:

$ gdb /local/bin/python
(gdb) set breakpoint pending on
(gdb) break myfunction
(gdb) run myscript.py

GDB resolves the breakpoint when the extension is loaded and stops when the function is called. You can also set a pending breakpoint directly in your extension's source code, for example with break myfunction.c:50.

我想在 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.c 和 Parser/myreadline.c。

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

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

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

是的,您可以继承内置类,例如 int, list, dict 等。

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