在应用中嵌入Python
翻译: | gashero |
---|
前面的章节讨论如何扩展Python,如何生成适合的C库等。不过还有另一种情况:通过将Python嵌入C/C++应用以扩展程序的功能。Python嵌入实现了一些使用Python更合适的功能。这可以有很多用途,一个例子是允许用户裁减需要的Python功能。也可以用于默写使用Python编写更加方便的功能。
嵌入Python与扩展很像。扩展Python时,主程序是Python解释器,但是嵌入Python则主程序并不是Python的-是程序的其他部分调用Python来实现一些功能。
所以,如果要嵌入Python,你可以提供自己的主程序,这个主程序需要初始化Python解释器。至少需要调用函数 Py_Initialize() (对于MacOS,调用 PyMac_Initialize())。可以选择是否传入命令行参数到Python。然后你就可以在应用的任何地方调用Python解释器了。
有几种方法调用解释器:可以传递一个包含Python语句的字符串到 PyRun_SimpleString() ,也可以传递一个stdio文件指针和一个文件名(用于识别错误信息)到 PyRun_SimpleFile() 。你也可以调用前几章介绍的底层操作直接控制Python对象。
可以在目录 Demo/embed/ 中找到嵌入Python的例子。
目录
嵌入Python最简单的形式是使用高层次的接口。这个接口专门用于执行Python脚本,而不需要与应用程序直接交互。例子可以在一个文件中展示:
#includeint main(int argc, char* argv[]) { Py_Initialize(); PyRun_SimpleString("from time import time,ctimen" "print 'Today is',ctime(time())n"); Py_Finalize(); return 0; }
如上代码首先使用 Py_Initialize() 初始化Python解释器,随后执行硬编码中的Python脚本来打印日期和时间。最后 Py_Finalize() 关闭了解释器。在真实应用中,你可能希望从其他方式获取Python脚本,文件、编辑器、数据库等。从文件获取的方式更适合使用 PyRun_SimpleFile() 函数,可以省去分配内存空间和载入文件的麻烦。
高层次的接口可以方便的执行Python代码,但是交换数据就很麻烦。如果需要,你可以使用低层次的接口调用。虽然多写一些C代码,但是却可以完成很多功能。
仍然要提醒的是,Python的扩展与嵌入其实很像,尽管目的不同。前几章讨论的大多数问题在这里也同样适用。可以参考用C扩展Python时一些步骤:
- 转换Python类型到C类型
- 传递参数并调用C函数
- 转换返回值到Python
当嵌入Python时,接口需要做:
- 转换C数据到Python
- 调用Python接口程序来调用Python函数
- 转化返回值到C
有如你所见,数据转换的步骤用于跨语言的数据交换。唯一的不同是两次数据转换之间调用的函数。当扩展时,你调用C函数,当嵌入时,调用Python函数。
这一章不会讨论Python和C之间的数据转换。并且假设你会使用手册来处理错误,自此只会讨论与扩展解释器不同的部分,你可以到前面的章节找到需要的信息。
第一个程序是执行一段Python脚本中的函数。有如高层接口一节,Python解释器并不会自动与程序结合。
运行一段Python脚本中的函数的代码如下:
#includeint main(int argc, char *argv[]) { PyObject *pName, *pModule, *pDict, *pFunc; PyObject *pArgs, *pValue; int i; if (argc < 3) { fprintf(stderr,"Usage: call pythonfile funcname [args]n"); return 1; } Py_Initialize(); pName = PyString_FromString(argv[1]); /* Error checking of pName left out */ pModule = PyImport_Import(pName); Py_DECREF(pName); if (pModule != NULL) { pFunc = PyObject_GetAttrString(pModule, argv[2]); /* pFunc is a new reference */ if (pFunc && PyCallable_Check(pFunc)) { pArgs = PyTuple_New(argc - 3); for (i = 0; i < argc - 3; ++i) { pValue = PyInt_FromLong(atoi(argv[i + 3])); if (!pValue) { Py_DECREF(pArgs); Py_DECREF(pModule); fprintf(stderr, "Cannot convert argumentn"); return 1; } /* pValue reference stolen here: */ PyTuple_SetItem(pArgs, i, pValue); } pValue = PyObject_CallObject(pFunc, pArgs); Py_DECREF(pArgs); if (pValue != NULL) { printf("Result of call: %ldn", PyInt_AsLong(pValue)); Py_DECREF(pValue); } else { Py_DECREF(pFunc); Py_DECREF(pModule); PyErr_Print(); fprintf(stderr,"Call failedn"); return 1; } } else { if (PyErr_Occurred()) PyErr_Print(); fprintf(stderr, "Cannot find function \"%s\"n", argv[2]); } Py_XDECREF(pFunc); Py_DECREF(pModule); } else { PyErr_Print(); fprintf(stderr, "Failed to load \"%s\"n", argv[1]); return 1; } Py_Finalize(); return 0; }
这段代码从argv[1]中载入Python脚本,并且调用argv[2]中的函数,整数型的参数则是从argv数组后面得来的。如果编译和链接这个程序,执行如下脚本:
def multiply(a,b): print "Will compute",a,"times",b c=0 for i in range(0,a) c=c+b return c
结果将是:
$ call multiply multiply 3 2 Will compute 3 times 2 Result of call: 6
虽然这个程序的代码挺多的,但是大部分其实都是做数据转换和错误报告。主要关于嵌入Python的开始于:
Py_Initialize(); pName=PyString_FromString(argv[1]); /* Error checking of pName left out */ pModule=PyImport_Import(pName);
初始化解释器之后,使用 PyImport_Import() 导入模块。这个函数需要字符串作为参数,使用 PyString_FromString() 来构造:
pFunc=PyObject_GetAttrString(pModule,argv[2]); /* pFunc is a new reference */ if (pFunc && PyCallable_Check(pFunc)) { ... } Py_XDECREF(pFunc);
载入了模块以后,就可以通过 PyObject_GetAttrString() 来获取对象。如果名字存在并且可以执行则可以安全的调用它。程序随后构造参数元组,然后执行调用:
pValue=PyObject_CallObject(pFunc,pArgs);
函数调用之后,pValue要么是NULL,要么是返回值的对象引用。注意在检查完返回值之后要释放引用。
至今为止,嵌入的Python解释器还不能访问应用程序本身的功能。Python的API允许扩展嵌入的Python的解释器。所以,Python可以获得其所嵌入的程序的功能。这听起来挺麻烦的,其实并不是那样。只要简单的忘记是应用程序启动了Python解释器。
可以把程序看作一对功能的集合,可以写一些胶水代码来来让Python访问这些功能,有如你在写一个普通的Python扩展一样。例如:
static int numargs=0; /* Return the number of arguments of the application command line */ static PyObject* emb_numargs(PyObject *self, PyObject *args) { if(!PyArg_ParseTuple(args, ":numargs")) return NULL; return Py_BuildValue("i", numargs); } static PyMethodDef EmbMethods[] = { {"numargs", emb_numargs, METH_VARARGS, "Return the number of arguments received by the process."}, {NULL, NULL, 0, NULL} };
添加上面的代码到 main() 函数。同样,插入如下两个语句到 Py_Initialize() 函数之后:
numargs=argc; Py_InitModule("emb",EmbMethods);
这两行代码初始化numargs变量,并且使得 emb.numargs() 函数更加易于被Python嵌入的解释器所理解。通过这个扩展,Python脚本可以做如下事情:
import emb print "Number of arguments",emb.numargs()
在实际的应用程序中,方法需要导出API以供Python使用。
有时候需要将Python嵌入到C++程序中,而你必须有一些要注意的C++系统的细节,一般来说你要为这个程序写一个main()函数,然后使用C++编译器来编译和链接程序。而这里不需要因为使用C++而重新编译Python本身。
当 configure 脚本执行时,可以正确的生成动态链接库使用的导出符号,而这些却不会自动被嵌入的静态链接的Python所继承,至少是在Unix。这是用于静态链接运行库(libpython.a)并且需要载入动态扩展(.so)的方式。
问题是一些入口点是使用Python运行时定义的而仅供扩展模块使用。如果嵌入应用不使用任何这些入口点,一些链接器不会包含这些实体到最终可执行文件的符号表。一些附加的选项可以用于告知连接器不要删除这些符号。
对于不同的平台,想要正确的检测该使用何种参数是非常困难的,但是幸运的是Python配置好了这些值。只要通过已经安装的Python解释器,启动交互解释器然后执行如下会话即可:
>>> import distutils.sysconfig >>> distutils.sysconfig.get_config_var('LINKFORSHARED') '-Xlinker -export-dynamic'
字符串的内容就是生成的选项。如果字符串为空,则不需要任何的附加选项。LINKFORSHARED的定义与Python顶层Makefile中的同名变量相同。