扩展/嵌入常见问题解答¶
我可以在 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 等工具包装库的数据类型和函数。 SIP、CXX Boost 或 Weave 也是包装 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.stdout
和 sys.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.c
和 Parser/myreadline.c
。
如何查找未定义的 g++ 符号 __builtin_new 或 __pure_virtual?¶
要动态加载 g++ 扩展模块,您必须重新编译 Python,使用 g++ 重新链接它(更改 Python 模块 Makefile 中的 LINKCC),并使用 g++ 链接扩展模块(例如,g++ -shared -o mymodule.so mymodule.o
)。
我能否创建一个对象类,其中一些方法在 C 中实现,而另一些方法在 Python 中实现(例如,通过继承)?¶
是的,您可以继承内置类,例如 int
、list
、dict
等。
Boost Python 库 (BPL, https://boost.ac.cn/libs/python/doc/index.html) 提供了一种从 C++ 中执行此操作的方法(即,您可以使用 BPL 继承自用 C++ 编写的扩展类)。