嵌套python解释器(Embedding Python in Another Application)

hello,world

[dongsong@bogon python_study]$ cat py.cpp 
#include <Python.h>
int main(int argc, char** argv)
{
        Py_Initialize();
        PyRun_SimpleString("import sys");
        PyRun_SimpleString("sys.path.append('./')");
        PyObject* pModule = PyImport_ImportModule("helloword");
        PyObject* pFunc = PyObject_GetAttrString(pModule, "hello");
        PyEval_CallObject(pFunc, NULL);
        Py_Finalize();
        return 0;
}
[dongsong@bogon python_study]$ cat helloword.py
def hello():
        print 'hello,world!'
[dongsong@bogon python_study]$ g++ -o py py.cpp  -I/home/dongsong/venv/include/python2.6/ -lpython2.6
[dongsong@bogon python_study]$ ./py
hello,world!
官方原文档 http://docs.python.org/3/extending/embedding.html
5. 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,我们需要提供我们自己的主程序。主程序需要做的事情之一就是初始化Python解释器。最起码,我们要调用函数Py_Initialize().我们也可以选择把命令行参数传到Python里面去。然后我们可以从应用的任何地方调用解释器。

这里有几种调用解释器的不同方式:把包含Python语句的字符串传给PyRun_SimpleString();或者把一个stdio文件指针和文件名(仅仅为了错误信息的识别)传给PyRun_SimpleFile().我们也可以调用前面章节描述的低级(lower-level)操作来构建和使用Python对象。

5.1. Very High Level Embedding

最简单的内嵌Python的形式是使用高级接口(the verf high level interface).这个接口执行Python脚本,而不需要和应用直接交互。这适用于对文件做操作的案例。

#include <Python.h>

int
main(int argc, char *argv[])
{
  Py_SetProgramName(argv[0]);  /* optional but recommended */
  Py_Initialize();
  PyRun_SimpleString("from time import time,ctime\n"
                     "print('Today is', ctime(time()))\n");
  Py_Finalize();
  return 0;
}

Py_SetProgramName()应该在Py_Initialize()之前调用,用于把Python运行时库(Python run-time libraries)的路径告诉解释器。接下来,Python解释器用Py_Initialize()初始化,再然后执行硬编码的Python脚本和输出日期时间。接着,Py_Finalize()关闭解释器,程序退出。对于一个实际程序,我们可能需要从另一个地方获取python代码,可能是一个文本编辑器,一个文件,或者一个数据库。从一个文件获取Python代码的话使用PyRun_SimpleFile()更合适,它省掉了我们分配内存和读取文件内容的麻烦。

5.2. Beyond Very High Level Embedding: An overview

高级接口让我们在应用中可以执行任意的代码片段,但是交换数据是相当麻烦的。如果有需求,我们应该使用低级接口(lower level calls)。虽然多写了一些C代码,但我们可以做到更多的事情。

注意,除了目的不一样,扩展Python和内嵌Python还是挺相似的(is quite the same activity)。之前章节讨论的大部分话题在这里依然有效。为了展示这一点,考虑一下,扩展Python的C代码实际上干了什么:

1.从Python到C转换数据,

2.用转换后的数据执行C程序的函数调用(Perform a function call to a C routine using the converted values),

3.把函数返回值从C转换到Python

内嵌Python,接口代码要做的事情:

1.从C到Python转换数据,

2.使用转换后的数据执行Python接口程序的函数调用(Perform a function call to a Python interface routine using the converted values),

3.把函数返回值从Python转换到C

可以看到,数据转换这一步只是对跨语言传递在不同方向上的简单包装。仅有的区别是在两次数据转换中间调用的程序。扩展时我们调用一个C程序,内嵌时我们调用一个Python程序。

这个章节我们不讨论怎么把数据从Python转换到C或者反方向转。引用的合理使用及错误处理也假定我们都明白了。因为这些跟扩展解释器没什么区别,我们可以参考前面章节了解这些信息。

5.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_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 = 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,http://docs.python.org/3/extending/embedding.html#compiling)这个程序(最终的可执行文件我们命名为"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_FromString(argv[1]);
/* Error checking of pName left out */
pModule = PyImport_Import(pName);
初始化解释器之后,脚本用PyImport_Import()(http://docs.python.org/3/c-api/import.html#PyImport_Import)导入。这个函数需要一个Python字符串作为它的参数,该Python字符串是用PyUnicode_FromString()(http://docs.python.org/3/c-api/unicode.html#PyUnicode_FromString)数据转换函数来构建的。

pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc is a new reference */

if (pFunc && PyCallable_Check(pFunc)) {
    ...
}
Py_XDECREF(pFunc);
一旦脚本被加载,需要要找的名字可以通过PyObject_GetAttrString()(http://docs.python.org/3/c-api/object.html#PyObject_GetAttrString)来获得。如果名字存在,并且返回的对象是可调用的(callable),我们可以安全的认定它是个函数。然后程序就正常执行,构建一个元组参数。再然后,调用Python函数:

pValue = PyObject_CallObject(pFunc, pArgs);
函数返回后,pValue要嘛是NULL,要嘛包含函数返回值的引用。检测完返回值以后记得释放引用!

5.4. Extending Embedded Python

到目前为止,嵌套的Python解释器还没有访问应用自身的功能。Python API通过扩展Python解释器提供这种访问。也就是说,嵌入的Python解释器被应用提供的函数(routines)做了扩展。听起来很复杂,其实也没那么夸张。暂时忘记应用启动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())
在一个实际的应用里面,这些方法会暴露应用的一个API给Python.

5.5. Embedding Python in C++

把Python嵌入到C++程序中也是可以的;至于如何实现就看系统C++的细节了;一般来讲,我们需要用C++写主程序并且用C++编译器编译和链接我们的程序。不需要用C++重新编译Python.

5.6. Compiling and Linking under Unix-like systems

对于把Python解释器嵌入到我们的应用里面来,没有必要专门为编译器和链接器寻找正确的标记(find the right flags to pass to your compiler(and linker)),因为Python需要加载的库模块是用C实现的动态扩展(C dynamic extensions).

为了找出需要的编译器和链接器标记,我们可以执行PythonX.Y-config脚本,这个脚本是作为安装过程的一部分而生成的(python3-config脚本可能也是可用的)。这个脚本有几个选项,其中下面几项的作用对我们来说将会更加直接:

pythonX.Y-config --cflags 为我们编译提供推荐的标记:

$ /opt/bin/python3.3-config --cflags
-I/opt/include/python3.3m -I/opt/include/python3.3m -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
pythonX.Y-config --ldflags 为我们链接提供推荐的标记:

$ /opt/bin/python3.3-config --ldflags
-L/opt/lib/python3.3/config-3.3m -lpthread -ldl -lutil -lm -lpython3.3m -Xlinker -export-dynamic

注意:为了避免多个已安装Python(尤其是系统Python和我们自己编译的Python)之间的混乱(confusion),在上述例子中,我们使用pythonX.Y-config时应该使用绝对路径。

如果这个程序不好使(不保证这个程序在所有类Unix(Unix-like)平台上都正常;然而我们欢迎汇报BUG(http://docs.python.org/3/bugs.html#reporting-bugs)),我们将不得不阅读我们的系统关于动态链接的文档和(或者)检查Python的Makefile(用sysconfig.get_makefile_filename()(http://docs.python.org/3/library/sysconfig.html#sysconfig.get_makefile_filename)来找到它的位置)和编译选项。在这种情况下,sysconfig(http://docs.python.org/3/library/sysconfig.html#module-sysconfig)模块是一个有用的工具,这个工具可以帮助我们以编程的方式提取我们想要合并到一起的配置的值。例如:

>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl  -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章