扩展/嵌入常见问题¶
目錄
可以使用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 语句?¶
The highest-level function to do this is PyRun_SimpleString()
which takes
a single string argument to be executed in the context of the module
__main__
and returns 0
for success and -1
when an exception occurred
(including SyntaxError
). If you want more control, use
PyRun_String()
; see the source for PyRun_SimpleString()
in
Python/pythonrun.c
.
如何在 C 中对任意 Python 表达式求值?¶
可以调用前一问题中介绍的函数 PyRun_String()
并附带起始标记符 Py_eval_input
;它会解析表达式,对其求值并返回结果值。
如何从Python对象中提取C的值?¶
这取决于对象的类型。 如果是元组,PyTuple_Size()
可返回其长度而 PyTuple_GetItem()
可返回指定序号上的项。 对于列表也有类似的函数 PyListSize()
和 PyList_GetItem()
。
对于字节串,PyBytes_Size()
可返回其长度而 PyBytes_AsStringAndSize()
提供一个指向其值和长度的指针。 请注意 Python 字节串可能为空,因此 C 的 strlen()
不应被使用。
To test the type of an object, first make sure it isn’t NULL, and then use
PyBytes_Check()
, PyTuple_Check()
, PyList_Check()
, etc.
还有一个针对 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失败了?¶
安装程序必须以换行符结束,如果没有换行符,则构建过程将失败。 (修复这个需要一些丑陋的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
模块,该模块非常接近解析器的行为。例如,IDLE就使用了这个。
在C中执行此操作的最简单方法是调用 PyRun_InteractiveLoop()
(可能在单独的线程中)并让Python解释器为您处理输入。您还可以设置 PyOS_ReadlineFunctionPointer()
指向您的自定义输入函数。有关更多提示,请参阅 Modules/readline.c
和 Parser/myreadline.c
。
但是,有时必须在与其他应用程序相同的线程中运行嵌入式Python解释器,并且不能允许 PyRun_InteractiveLoop()
在等待用户输入时停止。那么另一个解决方案是调用 PyParser_ParseString()
并测试 e.error
等于 E_EOF
,如果等于,就意味着输入不完整。这是一个示例代码片段,未经测试,灵感来自Alex Farber的代码:
#include <Python.h>
#include <node.h>
#include <errcode.h>
#include <grammar.h>
#include <parsetok.h>
#include <compile.h>
int testcomplete(char *code)
/* code should end in \n */
/* return -1 for error, 0 for incomplete, 1 for complete */
{
node *n;
perrdetail e;
n = PyParser_ParseString(code, &_PyParser_Grammar,
Py_file_input, &e);
if (n == NULL) {
if (e.error == E_EOF)
return 0;
return -1;
}
PyNode_Free(n);
return 1;
}
另一个解决方案是尝试使用 Py_CompileString()
编译接收到的字符串。如果编译时没有出现错误,请尝试通过调用 PyEval_EvalCode()
来执行返回的代码对象。否则,请将输入保存到以后。如果编译失败,找出是错误还是只需要更多的输入-从异常元组中提取消息字符串,并将其与字符串 “分析时意外的EOF” 进行比较。下面是使用GNUreadline库的完整示例(您可能希望在调用readline()时忽略 SIGINT ):
#include <stdio.h>
#include <readline.h>
#include <Python.h>
#include <object.h>
#include <compile.h>
#include <eval.h>
int main (int argc, char* argv[])
{
int i, j, done = 0; /* lengths of line, code */
char ps1[] = ">>> ";
char ps2[] = "... ";
char *prompt = ps1;
char *msg, *line, *code = NULL;
PyObject *src, *glb, *loc;
PyObject *exc, *val, *trb, *obj, *dum;
Py_Initialize ();
loc = PyDict_New ();
glb = PyDict_New ();
PyDict_SetItemString (glb, "__builtins__", PyEval_GetBuiltins ());
while (!done)
{
line = readline (prompt);
if (NULL == line) /* Ctrl-D pressed */
{
done = 1;
}
else
{
i = strlen (line);
if (i > 0)
add_history (line); /* save non-empty lines */
if (NULL == code) /* nothing in code yet */
j = 0;
else
j = strlen (code);
code = realloc (code, i + j + 2);
if (NULL == code) /* out of memory */
exit (1);
if (0 == j) /* code was empty, so */
code[0] = '\0'; /* keep strncat happy */
strncat (code, line, i); /* append line to code */
code[i + j] = '\n'; /* append '\n' to code */
code[i + j + 1] = '\0';
src = Py_CompileString (code, "<stdin>", Py_single_input);
if (NULL != src) /* compiled just fine - */
{
if (ps1 == prompt || /* ">>> " or */
'\n' == code[i + j - 1]) /* "... " and double '\n' */
{ /* so execute it */
dum = PyEval_EvalCode (src, glb, loc);
Py_XDECREF (dum);
Py_XDECREF (src);
free (code);
code = NULL;
if (PyErr_Occurred ())
PyErr_Print ();
prompt = ps1;
}
} /* syntax error or E_EOF? */
else if (PyErr_ExceptionMatches (PyExc_SyntaxError))
{
PyErr_Fetch (&exc, &val, &trb); /* clears exception! */
if (PyArg_ParseTuple (val, "sO", &msg, &obj) &&
!strcmp (msg, "unexpected EOF while parsing")) /* E_EOF */
{
Py_XDECREF (exc);
Py_XDECREF (val);
Py_XDECREF (trb);
prompt = ps2;
}
else /* some other syntax error */
{
PyErr_Restore (exc, val, trb);
PyErr_Print ();
free (code);
code = NULL;
prompt = ps1;
}
}
else /* some non-syntax error */
{
PyErr_Print ();
free (code);
code = NULL;
prompt = ps1;
}
free (line);
}
}
Py_XDECREF(glb);
Py_XDECREF(loc);
Py_Finalize();
exit(0);
}
如何找到未定义的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,http://www.boost.org/libs/python/doc/index.html)提供了一种从C ++执行此操作的方法(即,您可以使用BPL继承自C ++编写的扩展类 )。