1. 在其它应用程序嵌入 Python

    Python 的嵌入类似于扩展,但不完全相同。不同之处在于,扩展 Python 时应用程序的主程序仍然是 Python 解释器,而嵌入 Python 时的主程序可能与 Python 完全无关——而是应用程序的某些部分偶尔会调用 Python 解释器来运行一些 Python 代码。

    因此,若要嵌入 Python,就要提供自己的主程序。此主程序要做的事情之一就是初始化 Python 解释器。至少得调用函数 Py_Initialize()。还有些可选的调用可向 Python 传递命令行参数。之后即可从应用程序的任何地方调用解释器了。

    调用解释器的方式有好几种:可向 传入一个包含 Python 语句的字符串,也可向 PyRun_SimpleFile() 传入一个 stdio 文件指针和一个文件名(仅在错误信息中起到识别作用)。还可以调用前面介绍过的底层操作来构造并使用 Python 对象。

    参见

    本文详细介绍了 Python 的 C 接口。这里有大量必要的信息。

    最简单的 Python 嵌入形式就是采用非常高层的接口。该接口的目标是只执行一段 Python 脚本,而无需与应用程序直接交互。比如以下代码可以用来对某个文件进行一些操作。

    Py_Initialize() 之前,应该先调用 函数,以便向解释器告知 Python运行库的路径。接下来,Py_Initialize() 会初始化 Python 解释器,然后执行硬编码的 Python 脚本,打印出日期和时间。之后,调用 关闭解释器,程序结束。在真实的程序中,可能需要从其他来源获取 Python 脚本,或许是从文本编辑器例程、文件,或者某个数据库。利用 PyRun_SimpleFile() 函数可以更好地从文件中获取 Python 代码,可省去分配内存空间和加载文件内容的麻烦。

    1.2. 突破高层次嵌入的限制:概述

    高级接口能从应用程序中执行任何 Python 代码,但至少交换数据可说是相当麻烦的。如若需要交换数据,应使用较低级别的调用。几乎可以实现任何功能,代价是得写更多的 C 代码。

    应该注意,尽管意图不同,但扩展 Python 和嵌入 Python 的过程相当类似。前几章中讨论的大多数主题依然有效。为了说明这一点,不妨来看一下从 Python 到 C 的扩展代码到底做了什么:

    1. 转换 Python 的数据值到 C,

    2. 用转换后的数据执行 C 程序的函数调用,以及

    嵌入 Python 时,接口代码会这样做:

    1. 转换 C 的数据值到 Python,

    2. 用转换后的数据执行对 Python 接口的函数调用,

    3. 将调用返回的数据从 Python 转换为 C 格式。

    可见只是数据转换的步骤交换了一下顺序,以顺应跨语言的传输方向。唯一的区别是在两次数据转换之间调用的函数不同。在执行扩展时,调用一个 C 函数,而执行嵌入时调用的是个 Python 函数。

    本文不会讨论如何将数据从 Python 转换到 C 去,反之亦然。另外还假定读者能够正确使用引用并处理错误。由于这些地方与解释器的扩展没有区别,请参考前面的章节以获得所需的信息。

    第一个程序的目标是执行 Python 脚本中的某个函数。就像高层次接口那样,Python 解释器并不会直接与应用程序进行交互(但下一节将改变这一点)。

    要运行 Python 脚本中定义的函数,代码如下:

    1. int
    2. main(int argc, char *argv[])
    3. {
    4. PyObject *pName, *pModule, *pFunc;
    5. PyObject *pArgs, *pValue;
    6. int i;
    7. if (argc < 3) {
    8. fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
    9. return 1;
    10. }
    11. Py_Initialize();
    12. pName = PyUnicode_DecodeFSDefault(argv[1]);
    13. /* Error checking of pName left out */
    14. pModule = PyImport_Import(pName);
    15. Py_DECREF(pName);
    16. if (pModule != NULL) {
    17. pFunc = PyObject_GetAttrString(pModule, argv[2]);
    18. /* pFunc is a new reference */
    19. if (pFunc && PyCallable_Check(pFunc)) {
    20. pArgs = PyTuple_New(argc - 3);
    21. for (i = 0; i < argc - 3; ++i) {
    22. pValue = PyLong_FromLong(atoi(argv[i + 3]));
    23. if (!pValue) {
    24. Py_DECREF(pArgs);
    25. Py_DECREF(pModule);
    26. return 1;
    27. }
    28. /* pValue reference stolen here: */
    29. PyTuple_SetItem(pArgs, i, pValue);
    30. }
    31. pValue = PyObject_CallObject(pFunc, pArgs);
    32. Py_DECREF(pArgs);
    33. if (pValue != NULL) {
    34. printf("Result of call: %ld\n", PyLong_AsLong(pValue));
    35. Py_DECREF(pValue);
    36. }
    37. Py_DECREF(pFunc);
    38. Py_DECREF(pModule);
    39. PyErr_Print();
    40. fprintf(stderr,"Call failed\n");
    41. return 1;
    42. }
    43. }
    44. else {
    45. if (PyErr_Occurred())
    46. PyErr_Print();
    47. fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
    48. }
    49. Py_XDECREF(pFunc);
    50. Py_DECREF(pModule);
    51. }
    52. else {
    53. PyErr_Print();
    54. fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
    55. return 1;
    56. }
    57. if (Py_FinalizeEx() < 0) {
    58. return 120;
    59. }
    60. return 0;
    61. }

    上述代码先利用 argv[1] 加载 Python 脚本,再调用 argv[2] 指定的函数。函数的整数参数是 argv 数组中的其余值。如果 编译并链接 该程序(此处将最终的可执行程序称作 call), 并用它执行一个 Python 脚本,例如:

    1. def multiply(a,b):
    2. print("Will compute", a, "times", b)
    3. c = 0
    4. for i in range(0, a):
    5. c = c + b
    6. return c

    然后结果应该是:

    1. $ call multiply multiply 3 2
    2. Result of call: 6

    尽管相对其功能而言,该程序体积相当庞大,但大部分代码是用于 Python 和 C 之间的数据转换,以及报告错误。嵌入 Python 的有趣部分从此开始:

    初始化解释器之后,则用 加载脚本。此函数的参数需是个 Python 字符串,一个用 PyUnicode_FromString() 数据转换函数构建的字符串。

    1. pFunc = PyObject_GetAttrString(pModule, argv[2]);
    2. /* pFunc is a new reference */
    3. if (pFunc && PyCallable_Check(pFunc)) {
    4. ...
    5. }
    1. pValue = PyObject_CallObject(pFunc, pArgs);

    Upon return of the function, pValue is either NULL or it contains a reference to the return value of the function. Be sure to release the reference after examining the value.

    1.4. 对嵌入 Python 功能进行扩展

    到目前为止,嵌入的 Python 解释器还不能访问应用程序本身的功能。Python API 通过扩展嵌入解释器实现了这一点。 也就是说,用应用程序提供的函数对嵌入的解释器进行扩展。虽然听起来有些复杂,但也没那么糟糕。只要暂时忘记是应用程序启动了 Python 解释器。而把应用程序看作是一堆子程序,然后写一些胶水代码让 Python 访问这些子程序,就像编写普通的 Python 扩展程序一样。 例如:

    1. static int numargs=0;
    2. /* Return the number of arguments of the application command line */
    3. static PyObject*
    4. emb_numargs(PyObject *self, PyObject *args)
    5. {
    6. if(!PyArg_ParseTuple(args, ":numargs"))
    7. return NULL;
    8. return PyLong_FromLong(numargs);
    9. }
    10. static PyMethodDef EmbMethods[] = {
    11. {"numargs", emb_numargs, METH_VARARGS,
    12. "Return the number of arguments received by the process."},
    13. {NULL, NULL, 0, NULL}
    14. };
    15. static PyModuleDef EmbModule = {
    16. PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
    17. NULL, NULL, NULL, NULL
    18. };
    19. static PyObject*
    20. PyInit_emb(void)
    21. {
    22. return PyModule_Create(&EmbModule);
    23. }

    main() 函数之前插入上述代码。并在调用 Py_Initialize() 之前插入以下两条语句:

    这两行代码初始化了 numargs 变量,并让 emb.numargs() 函数能被嵌入的 Python 解释器访问到。有了这些扩展,Python 脚本可以执行类似以下功能:

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

    在真实的应用程序中,这种方法将把应用的 API 暴露给 Python 使用。

    还可以将 Python 嵌入到 C++ 程序中去;确切地说,实现方式将取决于 C++ 系统的实现细节;一般需用 C++ 编写主程序,并用 C++ 编译器来编译和链接 程序。不需要用 C++ 重新编译 Python 本身。

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

    为了将 Python 解释器嵌入应用程序,找到正确的编译参数传给编译器 (和链接器) 并非易事,特别是因为 Python 加载的库模块是以 C 动态扩展(.so 文件)的形式实现的。

    为了得到所需的编译器和链接器参数,可执行 python*X.Y*-config 脚本,它是在安装 Python 时生成的(也可能存在 python3-config 脚本)。该脚本有几个参数,其中以下几个参数会直接有用:

    • pythonX.Y-config --cflags 将给出建议的编译参数。

      1. $ /opt/bin/python3.4-config --cflags
      2. -I/opt/include/python3.4m -I/opt/include/python3.4m -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
    • pythonX.Y-config --ldflags 将给出建议的链接参数。

      1. $ /opt/bin/python3.4-config --ldflags
      2. -L/opt/lib/python3.4/config-3.4m -lpthread -ldl -lutil -lm -lpython3.4m -Xlinker -export-dynamic

    注解

    为了避免多个 Python 安装版本引发混乱(特别是在系统安装版本和自己编译版本之间),建议用 指定绝对路径,如上例所述。