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

前面的章节讨论了如何扩展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参考手册

本手册给出了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_SetProgramName() 函数应该在 Py_Initialize() 通知解释器到Python运行时库的路径。接下来,用初始化python解释器 Py_Initialize() 然后执行硬编码的python脚本,打印日期和时间。之后, Py_FinalizeEx() 调用关闭解释器,然后程序结束。在一个真正的程序中,您可能希望从另一个源(可能是文本编辑器例程、文件或数据库)获取python脚本。从文件中获取python代码最好使用 PyRun_SimpleFile() 函数,这样就省去了分配内存空间和加载文件内容的麻烦。

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。

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

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

1.3. 纯嵌入

第一个程序旨在执行Python脚本中的函数。与非常高级的接口部分一样,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] ,并调用 argv[2] . 它的整数参数是 argv 数组。如果你 compile and link 这个程序(让我们调用完成的可执行文件 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

尽管该程序的功能相当大,但大多数代码都是用于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通过扩展嵌入式解释器来实现这一点。也就是说,嵌入式解释器通过应用程序提供的例程进行扩展。虽然听起来很复杂,但也不错。暂时忘记应用程序启动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解释器可以访问的函数。有了这些扩展,python脚本可以做如下的事情

import emb
print("Number of arguments", emb.numargs())

在实际的应用程序中,这些方法将向Python公开应用程序的API。

1.5. C++中嵌入Python

也可以将Python嵌入到C++程序中,这是如何完成的,这取决于所使用的C++系统的细节;一般来说,您需要在C++中编写主程序,并使用C++编译器编译和链接程序。不需要使用C++来重新编译Python本身。

1.6. 在类Unix系统下编译和链接

为了将python解释器嵌入到应用程序中,找到传递给编译器(和链接器)的正确标志并不一定很简单,特别是因为python需要加载作为C动态扩展实现的库模块。 (.so 文件)。

要查找所需的编译器和链接器标志,可以执行 python{X.Y}-config 作为安装过程的一部分生成的脚本(A 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'