1.在另一个应用程序嵌入Python

前面的章节讨论了如何扩展Python,即如何通过附加C函数库来扩展Python的功能。反过来也是可行的:通过嵌入Python来丰富你的C / C ++应用程序嵌入为您的应用程序提供了在Python而不是Cor C ++中实现应用程序的某些功能的能力。这可以用于许多目的;一个例子是允许用户通过在Python中编写一些脚本来定制应用程序以满足他们的需求。你可以自己使用它,如果一些功能可以用Pythonmore轻松编写.

嵌入Python类似于扩展它,但并不完全。不同之处在于,当你扩展Python时,应用程序的主程序仍然是thePython解释器,而如果嵌入Python,主程序可能与Python无关 – 相反,应用程序的某些部分偶尔会调用Python解释器来运行Pythoncode.

所以,如果你正在嵌入Python,你提供自己的主程序。这个主程序必须做的一件事是初始化Python解释器。至少,你必须调用函数Py_Initialize()。有一些调用将命令行参数传递给Python。然后你可以从应用程序的任何部分调用解释器.

有几种不同的方法来调用解释器:你可以将包含字符串的Python语句传递给PyRun_SimpleString(),或者你可以传递astdio文件指针和文件名(仅用于在错误消息中标识)到PyRun_SimpleFile()。您还可以调用前面章节中描述的低级操作来构造和使用Python对象.

也可以看看

Python / C API参考手册
本手册中给出了Python C接口的详细信息。这里可以找到大量必要的信息.

 

1.1。非常高级嵌入

嵌入Python的最简单形式是使用非常高级的接口。此接口旨在执行Python脚本,而无需直接与应用程序交互。例如,这可用于对文件执行某些操作.

#include <Python.h>intmain(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_SetProgramName()函数应该在Py_Initialize()通知解释器有关Python运行时库的路径。接下来,使用Py_Initialize()然后执行一个硬编码的Python脚本,打印日期和时间。之后,Py_FinalizeEx()打电话关闭解释器,然后是程序结束。在实际程序中,您可能希望从其他源获取Python脚本,可能是文本编辑器例程,文件或数据库。通过使用PyRun_SimpleFile()函数可以更好地从文件中获取Python代码,这样可以节省分配内存空间和加载文件内容的麻烦.

 

1.2。超高级嵌入:概述

高级接口使您能够从应用程序执行任意部分的Python代码,但交换数据值至少可以说是非常麻烦的。如果你想要它,你应该使用较低级别的调用。需要编写更多C代码的成本,你几乎可以实现任何东西.

应该注意扩展Python和嵌入Python是完全相同的活动,尽管意图不同。前面章节中讨论的大多数主题仍然有效。为了说明这一点,请考虑从Python到C的扩展代码到底是什么:

  1. 将数据值从Python转换为C,
  2. 使用转换后的值执行C程序的函数调用,
  3. 将数据值从C调用转换为Python.

嵌入Python时,接口代码执行:

  1. 将数据值从C转换为Python,
  2. 使用转换值对Python接口例程执行函数调用,并且
  3. 将调用中的数据值从Python转换为C.

如您所见,数据转换步骤只是交换以适应跨语言传输的不同方向。唯一的区别是您在两次数据转换之间调用的例程扩展时,调用aC例程,嵌入时,调用Python例程.

本章不讨论如何将数据从Python转换为C,反之亦然。此外,假设正确使用引用和处理错误。由于这些方面与扩展解释器没有区别,您可以参考前面的章节获取所需的信息.

 

1.3。纯嵌入

第一个程序旨在在Python脚本中执行一个函数。就像非常高级别的接口一样,Python解释器不直接与应用程序交互(但在下一节中会有所改变).

运行Python脚本中定义的函数的代码是:

#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数组的其他值。如果你编译并链接这个程序(让我们调用完成的可执行文件调用),并用它来执行Pythonscript,例如:

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。扩展嵌入式Python

到目前为止,嵌入式Python解释器无法访问应用程序本身的功能。Python API通过扩展embeddedinterpreter来实现这一点。也就是说,嵌入解释器通过应用程序提供的例程进行扩展。虽然听起来很复杂,但并不是那么糟糕。简单地忘记应用程序启动Python解释器。相反,将应用程序视为一组子例程,并编写一些粘合代码,使Python能够访问这些例程,就像编写normalPython扩展一样。例如:

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解释器可以访问的函数。使用这些扩展,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

注意

为了避免在几个Python安装之间产生混淆(特别是在系统Python和你的系统之间)自己编译的Python),建议你使用pythonX.Y-config的绝对路径,如上例所示.

如果这个程序对你不起作用(不保证可以使用forall Unix)类似平台;但是,我们欢迎错误报告)你必须阅读你的系统关于动态链接和/ orexamine 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'

评论被关闭。