擴充/嵌入常見問題集¶
我可以在 C 中建立自己的函式嗎?¶
是的,你可以在 C 中建立包含函式、變數、例外甚至新型別的內建模組,擴充和嵌入 Python 直譯器 文件中有相關說明。
大多數中級或進階 Python 書籍也會涵蓋這個主題。
我可以在 C++ 中建立自己的函式嗎?¶
是的,可以使用 C++ 中兼容 C 的功能。 在 Python include 文件周围放置 extern "C" { ... }
,并在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
而在发生异常 (包括 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()
处理。
例如,使用引數 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 很難;還有其他選擇嗎?。
我使用安裝檔案新增了一個模組,但 make 失敗了;為什麼?¶
安装程序必须以换行符结束,如果没有换行符,则构建过程将失败。 (修复这个需要一些丑陋的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版本不包含 /usr/lib/python2.x/config/
目录,该目录中包含编译Python扩展所需的各种文件。
在 Red Hat 上,請安裝 python-devel RPM 來取得必要的檔案。
對於 Debian,運行 apt-get install python-dev
。
如何從「無效輸入」區分出「不完整輸入」?¶
有时,希望模仿Python交互式解释器的行为,在输入不完整时(例如,您键入了“if”语句的开头,或者没有关闭括号或三个字符串引号),给出一个延续提示,但当输入无效时,立即给出一条语法错误消息。
在 Python 中,你可以使用 codeop
模組,它充分模擬了剖析器 (parser) 的行為。像是 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 函式庫(BPL,https://www.boost.org/libs/python/doc/index.html)提供了一種從 C++ 執行此操作的方法(即你可以使用 BPL 來繼承用 C++ 編寫的擴充類別)。