扩展/嵌入常见问题¶
我可以用 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 之类的工具包装该库的数据类型和函数。SIP、CXX Boost 或 Weave 也是包装 C++ 库的替代方案。
我如何从 C 语言执行任意的 Python 语句?¶
执行此操作的最高级别函数是 PyRun_SimpleString()
,它接受一个要在 __main__
模块的上下文中执行的字符串参数,成功时返回 0
,发生异常时返回 -1
(包括 SyntaxError
)。如果您想要更多控制,请使用 PyRun_String()
;请参阅 Python/pythonrun.c
中 PyRun_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.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 脚本技巧,并且这个 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.c
和Parser/myreadline.c
。
我如何找到未定义的 g++ 符号 __builtin_new 或 __pure_virtual?¶
要动态加载 g++ 扩展模块,您必须重新编译 Python,使用 g++ 重新链接它(更改 Python Modules Makefile 中的 LINKCC),并使用 g++ 链接您的扩展模块(例如,g++ -shared -o mymodule.so mymodule.o
)。
我是否可以创建一个对象类,其中一些方法在 C 中实现,而另一些方法在 Python 中实现(例如通过继承)?¶
Boost Python Library (BPL, https://boost.ac.cn/libs/python/doc/index.html) 提供了一种从 C++ 执行此操作的方法(即,您可以使用 BPL 从 C++ 编写的扩展类继承)。