1. Embedding Python in Another Application¶
前面的章节讨论了如何扩展Python,即如何通过附加一个C函数库来扩展Python的功能。也可以通过嵌入Python来丰富你的C / C ++应用程序。嵌入为应用程序提供了在Python而不是C或C ++中实现应用程序的一些功能的能力。这可以用于许多目的;一个例子是允许用户通过在Python中编写一些脚本来根据自己的需要定制应用程序。你也可以自己使用它,如果一些功能可以更容易地用Python编写。
嵌入Python类似于扩展它,但不完全。不同的是,当你扩展Python时,应用程序的主程序仍然是Python解释器,而如果你嵌入Python,主程序可能与Python无关 - 相反,应用程序的某些部分偶尔调用Python解释器运行一些Python代码。
所以如果你嵌入Python,你提供自己的主程序。这个主要程序要做的事情之一是初始化Python解释器。至少,你必须调用函数Py_Initialize()
。有可选的调用来传递命令行参数到Python。然后,您可以从应用程序的任何部分调用解释器。
有几种不同的方法来调用解释器:你可以传递一个包含Python语句的字符串到PyRun_SimpleString()
,或者你可以传递一个stdio文件指针和一个文件名)到PyRun_SimpleFile()
。您还可以调用前面章节中描述的较低级操作来构造和使用Python对象。
也可以看看
- Python/C API Reference Manual
- 本手册中给出了Python的C接口的详细信息。在这里可以找到大量的必要信息。
1.1. Very High Level Embedding¶
嵌入Python的最简单的形式是使用非常高级的接口。此接口旨在执行Python脚本,而无需直接与应用程序交互。这可以例如用于对文件执行一些操作。
#include <Python.h>
int
main(int argc, char *argv[])
{
wchar_t *program = Py_DecodeLocale(argv[0], NULL);
if (program == NULL) {
fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
exit(1);
}
Py_SetProgramName(program); /* optional but recommended */
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is', ctime(time()))\n");
Py_Finalize();
PyMem_RawFree(program);
return 0;
}
应在Py_Initialize()
之前调用Py_SetProgramName()
函数,以通知解释器有关Python运行时库的路径。接下来,Python解释器用Py_Initialize()
初始化,然后执行打印日期和时间的硬编码Python脚本。然后,Py_Finalize()
调用关闭解释器,然后关闭程序结束。在实际程序中,您可能想从另一个来源获取Python脚本,可能是文本编辑器例程,文件或数据库。从文件中获取Python代码可以通过使用PyRun_SimpleFile()
函数来完成,这样可以节省分配内存空间和加载文件内容的麻烦。
1.2. Beyond Very High Level Embedding: An overview¶
高级接口使您能够从应用程序中执行任意的Python代码,但是交换数据值是相当麻烦的。如果你想要,你应该使用较低级别的调用。以写的更多的C代码为代价,你可以实现几乎任何东西。
应该注意,扩展Python和嵌入Python是完全相同的活动,尽管意图不同。前面章节中讨论的大多数主题仍然有效。为了说明这一点,考虑从Python到C的扩展代码真正做什么:
- 将数据值从Python转换为C,
- 使用转换的值执行对C例程的函数调用,和
- 将来自C的调用的数据值转换为Python。
当嵌入Python时,接口代码:
- 将数据值从C转换为Python,
- 使用转换的值执行对Python接口例程的函数调用,和
- 将来自Python的调用的数据值转换为C.
如您所见,数据转换步骤只是交换,以适应跨语言传输的不同方向。唯一的区别是您在两次数据转换之间调用的例程。当扩展时,你调用一个C程序,当嵌入时,你调用一个Python例程。
本章不讨论如何将数据从Python转换为C,反之亦然。此外,假设正确使用引用和处理错误。由于这些方面与扩展解释器没有区别,因此您可以参考前面的章节获取所需的信息。
1.3. Pure Embedding¶
第一个程序的目的是在Python脚本中执行一个函数。像在关于高层接口的部分一样,Python解释器不直接与应用程序交互(但是在下一节中将会改变)。
运行在Python脚本中定义的函数的代码是:
#include <Python.h>
int
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 = PyUnicode_DecodeFSDefault(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 = PyLong_FromLong(atoi(argv[i + 3]));
if (!pValue) {
Py_DECREF(pArgs);
Py_DECREF(pModule);
fprintf(stderr, "Cannot convert argument\n");
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: %ld\n", PyLong_AsLong(pValue));
Py_DECREF(pValue);
}
else {
Py_DECREF(pFunc);
Py_DECREF(pModule);
PyErr_Print();
fprintf(stderr,"Call failed\n");
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
数组的其他值。如果您compile and link此程序(让我们调用完成的可执行文件调用),并使用它来执行Python脚本,如:
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和C之间的数据转换,以及错误报告。有关嵌入Python的有趣的部分开始
Py_Initialize();
pName = PyUnicode_DecodeFSDefault(argv[1]);
/* Error checking of pName left out */
pModule = PyImport_Import(pName);
初始化解释器之后,使用PyImport_Import()
加载脚本。此例程需要一个Python字符串作为其参数,它使用PyUnicode_FromString()
数据转换程序构建。
pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc is a new reference */
if (pFunc && PyCallable_Check(pFunc)) {
...
}
Py_XDECREF(pFunc);
加载脚本后,使用PyObject_GetAttrString()
检索我们要查找的名称。如果名称存在,并且返回的对象是可调用的,则可以安全地假定它是一个函数。然后程序通过构建正常的参数的元组来进行。然后调用Python函数:
pValue = PyObject_CallObject(pFunc, pArgs);
在返回函数时,pValue
为NULL或包含对函数返回值的引用。确保在检查值后释放引用。
1.4. Extending Embedded Python¶
到目前为止,嵌入式Python解释器没有从应用程序本身访问功能。Python API允许通过扩展嵌入式解释器来实现。也就是说,嵌入式解释器被应用程序提供的例程扩展。虽然听起来很复杂,但并不是那么糟糕。只是忘了一会儿,应用程序启动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 PyLong_FromLong(numargs);
}
static PyMethodDef EmbMethods[] = {
{"numargs", emb_numargs, METH_VARARGS,
"Return the number of arguments received by the process."},
{NULL, NULL, 0, NULL}
};
static PyModuleDef EmbModule = {
PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
NULL, NULL, NULL, NULL
};
static PyObject*
PyInit_emb(void)
{
return PyModule_Create(&EmbModule);
}
将上述代码插入到main()
函数上方。此外,在调用Py_Initialize()
之前插入以下两个语句:
numargs = argc;
PyImport_AppendInittab("emb", &PyInit_emb);
这两行初始化numargs
变量,并使嵌入式Python解释器访问emb.numargs()
函数。有了这些扩展,Python脚本可以做
import emb
print("Number of arguments", emb.numargs())
在实际应用中,这些方法会将应用程序的API暴露给Python。
1.5. Embedding Python in C++¶
也可以将Python嵌入到C ++程序中;正是如何做这将取决于使用的C ++系统的细节;一般来说,你需要在C ++中编写主程序,并使用C ++编译器来编译和链接你的程序。没有必要使用C ++重新编译Python本身。
1.6. Compiling and Linking under Unix-like systems¶
为了将Python解释器嵌入到您的应用程序中,找到正确的标志传递给您的编译器(和链接器)不一定是微不足道的,特别是因为Python需要加载以C动态扩展实现的库模块(.so
文件)。
要找到所需的编译器和链接器标志,您可以执行生成的python X.Y -config
作为安装过程的一部分(也可以使用python3-config
脚本)。此脚本有几个选项,其中以下将直接对您有用:
在编译时,
pythonX.Y-config - cflags
$ /opt/bin/python3.4-config --cflags -I/opt/include/python3.4m -I/opt/include/python3.4m -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
pythonX.Y-config - ldflags
$ /opt/bin/python3.4-config --ldflags -L/opt/lib/python3.4/config-3.4m -lpthread -ldl -lutil -lm -lpython3.4m -Xlinker -export-dynamic
注意
为了避免在几个Python安装之间(尤其是在系统Python和你自己编译的Python之间)的混淆,建议使用python X.Y -config
,如上例所示。
如果这个过程对你不起作用(它不能保证适用于所有类Unix平台;但是,我们欢迎bug reports),你必须阅读系统的关于动态链接和/或者检查Python的Makefile
(使用sysconfig.get_makefile_filename()
找到它的位置)和编译选项。在这种情况下,sysconfig
模块是一个有用的工具,用于以编程方式提取您要组合在一起的配置值。例如:
>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'