On this page
1. 将 Python 嵌入另一个应用程序
前面的章节讨论了如何扩展 Python,即如何pass将 C 函数库附加到 Python 来扩展 Python 的Function。也可以pass其他方式实现:pass将 Python 嵌入到 C/C 应用程序中来丰富 C/C 应用程序。嵌入使您的应用程序能够用 Python 而不是 C 或 C 实现应用程序的某些Function。这可以用于许多目的。一个示例是允许用户pass使用 Python 编写一些脚本来根据自己的需求定制应用程序。如果某些Function可以更轻松地用 Python 编写,那么您也可以自己使用它。
嵌入 Python 与扩展 Python 类似,但不完全相同。区别在于,当您扩展 Python 时,应用程序的主程序仍为 Python 解释器,而如果您嵌入 Python,则主程序可能与 Python 无关—相反,应用程序的某些部分有时会调用 Python 解释器运行一些 Python 代码。
因此,如果您要嵌入 Python,那么您将提供自己的主程序。这个主程序要做的一件事是初始化 Python 解释器。至少,您必须调用函数Py_Initialize()。有一些可选的调用,可将命令行参数传递给 Python。然后,您可以从应用程序的任何部分调用解释器。
有几种不同的调用解释器的方法:您可以将包含 Python 语句的字符串传递给PyRun_SimpleString(),也可以将 stdio 文件指针和文件名(仅用于错误消息中的标识)传递给PyRun_SimpleFile()。您还可以调用前面各章中描述的较低级别的操作来构造和使用 Python 对象。
See also
本手册中提供了 Python C 接口的详细信息。在这里可以找到大量必要的信息。
1.1. 非常高级的嵌入
嵌入 Python 的最简单形式是使用非常高级的接口。该接口旨在执行 Python 脚本,而无需直接与应用程序进行交互。例如,这可以用于对文件执行某些操作。
#define PY_SSIZE_T_CLEAN
#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");
if (Py_FinalizeEx() < 0) {
exit(120);
}
PyMem_RawFree(program);
return 0;
}
应该在Py_Initialize()之前调用Py_SetProgramName()函数,以向解释器通知 Python 运行时库的路径。接下来,使用Py_Initialize()初始化 Python 解释器,然后执行打印日期和时间的硬编码 Python 脚本。之后,Py_FinalizeEx()调用将关闭解释器,然后结束程序。在实际程序中,您可能希望从其他来源(可能是文本编辑器例程,文件或数据库)获取 Python 脚本。pass使用PyRun_SimpleFile()函数可以更好地从文件中获取 Python 代码,从而避免了分配内存空间和加载文件内容的麻烦。
1.2. 超越高级嵌入:概述
高级接口使您能够从应用程序中执行任意段 Python 代码,但是至少可以说,交换数据值非常麻烦。如果需要,应该使用较低级别的调用。以编写更多 C 代码为代价,您几乎可以实现任何目标。
应该注意的是,尽管意图不同,但扩展 Python 和嵌入 Python 是完全相同的活动。前几章讨论的大多数主题仍然有效。为了说明这一点,请考虑一下从 Python 到 C 的扩展代码的作用:
将数据值从 Python 转换为 C,
使用转换后的值执行对 C 例程的函数调用,然后
将调用中的数据值从 C 转换为 Python。
嵌入 Python 时,接口代码执行以下操作:
将数据值从 C 转换为 Python,
使用转换后的值执行对 Python 接口例程的函数调用,以及
将调用中的数据值从 Python 转换为 C。
如您所见,只需转换数据转换步骤即可适应跨语言传输的不同方向。唯一的区别是两次数据转换之间调用的例程。扩展时,您调用 C 例程,嵌入时,您调用 Python 例程。
本章将不讨论如何将数据从 Python 转换为 C,反之亦然。同样,假定理解了正确使用引用和处理错误。由于这些方面与扩展解释器没有什么不同,因此可以参考前面的章节以获取所需的信息。
1.3. 纯嵌入
第一个程序旨在在 Python 脚本中执行Function。就像在有关高级接口的部分中一样,Python 解释器不会直接与应用程序进行交互(但是在下一部分中会有所变化)。
运行 Python 脚本中定义的函数的代码为:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
int
main(int argc, char *argv[])
{
PyObject *pName, *pModule, *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;
}
if (Py_FinalizeEx() < 0) {
return 120;
}
return 0;
}
此代码使用argv[1]
加载 Python 脚本,并调用argv[2]
中命名的函数。它的整数参数是argv
数组的其他值。如果您编译链接此程序(让我们调用完成的可执行文件 call ),然后使用它执行 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
尽管该程序的Function非常庞大,但大多数代码都是用于 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()检索我们要查找的名称。如果名称存在,并且返回的对象是可调用的,则可以安全地假定它是一个函数。然后,程序pass正常构造参数 Tuplescontinue 进行。然后使用以下命令调用 Python 函数:
pValue = PyObject_CallObject(pFunc, pArgs);
函数返回时,pValue
是NULL
或包含对函数返回值的引用。检查值之后,请确保释放参考。
1.4. 扩展嵌入式 Python
到目前为止,嵌入式 Python 解释器无法从应用程序本身访问Function。 Python API pass扩展嵌入式解释器来实现这一点。也就是说,嵌入式解释器将使用应用程序提供的例程进行扩展。虽然听起来很复杂,但还不错。只需暂时忘记应用程序启动 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
变量,并使emb.numargs()
函数可供嵌入式 Python 解释器访问。pass这些扩展,Python 脚本可以执行以下操作
import emb
print("Number of arguments", emb.numargs())
在实际的应用程序中,这些方法会将应用程序的 API 公开给 Python。
1.5. 在 C 中嵌入 Python
也可以将 Python 嵌入 C 程序中。确切的说,这将取决于所用 C 系统的细节;通常,您将需要使用 C 编写主程序,并使用 C 编译器来编译和链接程序。无需使用 C 重新编译 Python 本身。
1.6. 类 Unix 系统下的编译和链接
找到正确的标志传递给编译器(和链接器)以将 Python 解释器嵌入到您的应用程序中并不一定很简单,尤其是因为 Python 需要加载实现为与它链接的 C 动态扩展(.so
文件)的库模块。
要找出所需的编译器和链接器标志,可以执行在安装过程中生成的pythonX.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
Note
为了避免在多个 Python 安装之间(尤其是系统 Python 和您自己的已编译 Python)之间的混淆,建议使用上例中的pythonX.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'