扩展/嵌入常见问题¶
我可以用 C 语言创建自己的函数吗?¶
是的,您可以在 C 语言中创建包含函数、变量、异常甚至新类型的内置模块。这在文档扩展和嵌入 Python 解释器中有所解释。
大多数中高级 Python 书籍也会涵盖这个主题。
我可以用 C++ 语言创建自己的函数吗?¶
是的,使用 C++ 中发现的 C 兼容性特性。将 extern "C" { ... }
放在 Python 头文件周围,并在将由 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()
等调用以及许多其他有用的协议(例如数字 (PyNumber_Index()
等) 和 PyMapping API 中的映射)与任何类型的 Python 序列进行接口。
如何使用 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 脚本 hack 技巧,而且这个 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 语言实现(例如通过继承)?¶
是的,您可以继承内置类,例如 int
、list
、dict
等。
Boost Python 库 (BPL, https://boost.ac.cn/libs/python/doc/index.html) 提供了一种从 C++ 执行此操作的方法(即,您可以使用 BPL 继承用 C++ 编写的扩展类)。