“Python/docs/3.9/extending/embedding”的版本间差异
(autoload) |
小 (Page commit) |
||
第1行: | 第1行: | ||
+ | {{DISPLAYTITLE:1. 在另一个应用程序中嵌入 Python — Python 文档}} | ||
<div id="embedding-python-in-another-application" class="section"> | <div id="embedding-python-in-another-application" class="section"> | ||
<span id="embedding"></span> | <span id="embedding"></span> | ||
− | = | + | = 1. 在另一个应用程序中嵌入 Python = |
− | + | 前面的章节讨论了如何扩展 Python,即如何通过附加 C 函数库来扩展 Python 的功能。 也可以反过来做:通过在其中嵌入 Python 来丰富您的 C/C++ 应用程序。 嵌入使您的应用程序能够使用 Python 而不是 C 或 C++ 来实现应用程序的某些功能。 这可以用于许多目的; 一个例子是允许用户通过用 Python 编写一些脚本来根据他们的需要定制应用程序。 如果某些功能可以更轻松地用 Python 编写,您也可以自己使用它。 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | 嵌入 Python 类似于扩展它,但不完全相同。 不同的是,当你扩展 Python 时,应用程序的主程序仍然是 Python 解释器,而如果你嵌入 Python,主程序可能与 Python 无关——相反,应用程序的某些部分偶尔会调用 Python 解释器运行一些 Python 代码。 | |
− | |||
− | Python | ||
− | |||
− | |||
− | + | 因此,如果您要嵌入 Python,您将提供自己的主程序。 这个主程序必须做的一件事是初始化 Python 解释器。 至少,您必须调用函数 [[../../c-api/init#c|Py_Initialize()]]。 有一些可选调用可以将命令行参数传递给 Python。 然后,您可以从应用程序的任何部分调用解释器。 | |
− | |||
− | |||
− | |||
− | |||
− | + | 有几种不同的调用解释器的方法:您可以将包含 Python 语句的字符串传递给 [[../../c-api/veryhigh#c|PyRun_SimpleString()]],或者您可以将 stdio 文件指针和文件名(仅用于在错误消息中识别)传递给[[../../c-api/veryhigh#c|PyRun_SimpleFile()]]。 您还可以调用前面章节中描述的低级操作来构造和使用 Python 对象。 | |
− | |||
− | stdio | ||
− | |||
− | |||
<div class="admonition seealso"> | <div class="admonition seealso"> | ||
− | + | 也可以看看 | |
− | ; [[../../c-api/index#c-api-index|<span class="std std-ref">Python/C API | + | ; [[../../c-api/index#c-api-index|<span class="std std-ref">Python/C API 参考手册</span>]] |
− | : | + | : 本手册中给出了 Python 的 C 接口的详细信息。 在这里可以找到大量必要的信息。 |
第44行: | 第25行: | ||
<span id="high-level-embedding"></span> | <span id="high-level-embedding"></span> | ||
− | == | + | == 1.1. 非常高级的嵌入 == |
− | + | 嵌入 Python 的最简单形式是使用非常高级的接口。 此接口旨在执行 Python 脚本,而无需直接与应用程序交互。 例如,这可用于对文件执行某些操作。 | |
− | |||
− | |||
− | |||
<div class="highlight-c notranslate"> | <div class="highlight-c notranslate"> | ||
第55行: | 第33行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="c">#define PY_SSIZE_T_CLEAN |
− | #include | + | #include <Python.h> |
int | int | ||
第63行: | 第41行: | ||
wchar_t *program = Py_DecodeLocale(argv[0], NULL); | wchar_t *program = Py_DecodeLocale(argv[0], NULL); | ||
if (program == NULL) { | if (program == NULL) { | ||
− | fprintf(stderr, | + | fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); |
exit(1); | exit(1); | ||
} | } | ||
Py_SetProgramName(program); /* optional but recommended */ | Py_SetProgramName(program); /* optional but recommended */ | ||
Py_Initialize(); | Py_Initialize(); | ||
− | PyRun_SimpleString( | + | PyRun_SimpleString("from time import time,ctime\n" |
− | + | "print('Today is', ctime(time()))\n"); | |
− | if (Py_FinalizeEx() | + | if (Py_FinalizeEx() < 0) { |
exit(120); | exit(120); | ||
} | } | ||
PyMem_RawFree(program); | PyMem_RawFree(program); | ||
return 0; | return 0; | ||
− | }</ | + | }</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | [[../../c-api/init#c|Py_SetProgramName()]] 函数应该在 [[../../c-api/init#c|Py_Initialize()]] 之前调用,以通知解释器有关 Python 运行时库的路径。 接下来,使用 [[../../c-api/init#c|Py_Initialize()]] 初始化 Python 解释器,然后执行打印日期和时间的硬编码 Python 脚本。 之后, [[../../c-api/init#c|Py_FinalizeEx()]] 调用关闭解释器,然后程序结束。 在实际程序中,您可能希望从其他来源(可能是文本编辑器例程、文件或数据库)获取 Python 脚本。 使用 [[../../c-api/veryhigh#c|PyRun_SimpleFile()]] 函数可以更好地从文件中获取 Python 代码,这样可以省去分配内存空间和加载文件内容的麻烦。 | |
− | [[../../c-api/init#c| | ||
− | |||
− | [[../../c-api/init#c| | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
第96行: | 第65行: | ||
<span id="lower-level-embedding"></span> | <span id="lower-level-embedding"></span> | ||
− | == | + | == 1.2. 超越非常高级的嵌入:概述 == |
− | + | 高级接口使您能够从应用程序执行任意 Python 代码段,但至少可以说交换数据值非常麻烦。 如果需要,您应该使用较低级别的调用。 以必须编写更多 C 代码为代价,您几乎可以实现任何目标。 | |
− | Python | ||
− | |||
− | |||
− | + | 应该注意的是,尽管意图不同,但扩展 Python 和嵌入 Python 是完全相同的活动。 前几章中讨论的大多数主题仍然有效。 为了说明这一点,请考虑从 Python 到 C 的扩展代码的真正作用: | |
− | |||
− | |||
− | Python | ||
− | # | + | # 将数据值从 Python 转换为 C, |
− | # | + | # 使用转换后的值对 C 例程执行函数调用,以及 |
− | # | + | # 将调用中的数据值从 C 转换为 Python。 |
− | + | 嵌入 Python 时,接口代码执行以下操作: | |
− | # | + | # 将数据值从 C 转换为 Python, |
− | # | + | # 使用转换后的值对 Python 接口例程执行函数调用,以及 |
− | # | + | # 将来自 Python 调用的数据值转换为 C。 |
− | + | 如您所见,数据转换步骤只是交换以适应跨语言传输的不同方向。 唯一的区别是您在两次数据转换之间调用的例程。 扩展时调用 C 例程,嵌入时调用 Python 例程。 | |
− | |||
− | |||
− | C | ||
− | + | 本章不会讨论如何将数据从 Python 转换为 C,反之亦然。 此外,假定已理解正确使用引用和处理错误。 由于这些方面与扩展解释器没有区别,因此您可以参考前面的章节以获取所需信息。 | |
− | |||
− | |||
− | |||
第133行: | 第90行: | ||
<span id="id1"></span> | <span id="id1"></span> | ||
− | == | + | == 1.3. 纯嵌入 == |
− | + | 第一个程序旨在执行 Python 脚本中的函数。 就像在关于非常高级接口的部分中一样,Python 解释器不直接与应用程序交互(但这将在下一节中更改)。 | |
− | |||
− | |||
− | |||
− | + | 运行 Python 脚本中定义的函数的代码是: | |
<div class="highlight-c notranslate"> | <div class="highlight-c notranslate"> | ||
第146行: | 第100行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="c">#define PY_SSIZE_T_CLEAN |
− | #include | + | #include <Python.h> |
int | int | ||
第156行: | 第110行: | ||
int i; | int i; | ||
− | if (argc | + | if (argc < 3) { |
− | fprintf(stderr, | + | fprintf(stderr,"Usage: call pythonfile funcname [args]\n"); |
return 1; | return 1; | ||
} | } | ||
第172行: | 第126行: | ||
/* pFunc is a new reference */ | /* pFunc is a new reference */ | ||
− | if (pFunc & | + | if (pFunc && PyCallable_Check(pFunc)) { |
pArgs = PyTuple_New(argc - 3); | pArgs = PyTuple_New(argc - 3); | ||
− | for (i = 0; i | + | for (i = 0; i < argc - 3; ++i) { |
pValue = PyLong_FromLong(atoi(argv[i + 3])); | pValue = PyLong_FromLong(atoi(argv[i + 3])); | ||
if (!pValue) { | if (!pValue) { | ||
Py_DECREF(pArgs); | Py_DECREF(pArgs); | ||
Py_DECREF(pModule); | Py_DECREF(pModule); | ||
− | fprintf(stderr, | + | fprintf(stderr, "Cannot convert argument\n"); |
return 1; | return 1; | ||
} | } | ||
第188行: | 第142行: | ||
Py_DECREF(pArgs); | Py_DECREF(pArgs); | ||
if (pValue != NULL) { | if (pValue != NULL) { | ||
− | printf( | + | printf("Result of call: %ld\n", PyLong_AsLong(pValue)); |
Py_DECREF(pValue); | Py_DECREF(pValue); | ||
} | } | ||
第195行: | 第149行: | ||
Py_DECREF(pModule); | Py_DECREF(pModule); | ||
PyErr_Print(); | PyErr_Print(); | ||
− | fprintf(stderr, | + | fprintf(stderr,"Call failed\n"); |
return 1; | return 1; | ||
} | } | ||
第202行: | 第156行: | ||
if (PyErr_Occurred()) | if (PyErr_Occurred()) | ||
PyErr_Print(); | PyErr_Print(); | ||
− | fprintf(stderr, | + | fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]); |
} | } | ||
Py_XDECREF(pFunc); | Py_XDECREF(pFunc); | ||
第209行: | 第163行: | ||
else { | else { | ||
PyErr_Print(); | PyErr_Print(); | ||
− | fprintf(stderr, | + | fprintf(stderr, "Failed to load \"%s\"\n", argv[1]); |
return 1; | return 1; | ||
} | } | ||
− | if (Py_FinalizeEx() | + | if (Py_FinalizeEx() < 0) { |
return 120; | return 120; | ||
} | } | ||
return 0; | return 0; | ||
− | }</ | + | }</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 此代码使用 <code>argv[1]</code> 加载 Python 脚本,并调用 <code>argv[2]</code> 中命名的函数。 它的整数参数是 <code>argv</code> 数组的其他值。 如果你[[#compiling|编译并链接]]这个程序(我们调用完成的可执行文件'''call'''),并用它来执行一个Python脚本,例如: | |
− | |||
− | |||
− | |||
− | |||
<div class="highlight-python notranslate"> | <div class="highlight-python notranslate"> | ||
第231行: | 第181行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python">def multiply(a,b): |
− | print( | + | print("Will compute", a, "times", b) |
c = 0 | c = 0 | ||
for i in range(0, a): | for i in range(0, a): | ||
c = c + b | c = c + b | ||
− | return c</ | + | return c</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 那么结果应该是: | |
<div class="highlight-shell-session notranslate"> | <div class="highlight-shell-session notranslate"> | ||
第247行: | 第197行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | <pre>$ call multiply multiply 3 2 | + | <pre class="session">$ call multiply multiply 3 2 |
Will compute 3 times 2 | Will compute 3 times 2 | ||
Result of call: 6</pre> | Result of call: 6</pre> | ||
第254行: | 第204行: | ||
</div> | </div> | ||
− | + | 尽管该程序的功能相当庞大,但大部分代码用于 Python 和 C 之间的数据转换以及错误报告。 关于嵌入 Python 的有趣部分始于 | |
− | |||
− | |||
<div class="highlight-c notranslate"> | <div class="highlight-c notranslate"> | ||
第262行: | 第210行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="c">Py_Initialize(); |
pName = PyUnicode_DecodeFSDefault(argv[1]); | pName = PyUnicode_DecodeFSDefault(argv[1]); | ||
/* Error checking of pName left out */ | /* Error checking of pName left out */ | ||
− | pModule = PyImport_Import(pName);</ | + | pModule = PyImport_Import(pName);</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 初始化解释器后,使用 [[../../c-api/import#c|PyImport_Import()]] 加载脚本。 该例程需要一个 Python 字符串作为其参数,该字符串是使用 [[../../c-api/unicode#c|PyUnicode_FromString()]] 数据转换例程构造的。 | |
− | [[../../c-api/import#c| | ||
− | |||
− | |||
<div class="highlight-c notranslate"> | <div class="highlight-c notranslate"> | ||
第279行: | 第224行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="c">pFunc = PyObject_GetAttrString(pModule, argv[2]); |
/* pFunc is a new reference */ | /* pFunc is a new reference */ | ||
− | if (pFunc & | + | if (pFunc && PyCallable_Check(pFunc)) { |
... | ... | ||
} | } | ||
− | Py_XDECREF(pFunc);</ | + | Py_XDECREF(pFunc);</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 加载脚本后,我们将使用 [[../../c-api/object#c|PyObject_GetAttrString()]] 检索我们要查找的名称。 如果名称存在,并且返回的对象是可调用的,则可以安全地假设它是一个函数。 然后程序继续像往常一样构造一个参数元组。 然后使用以下命令调用 Python 函数: | |
− | [[../../c-api/object#c| | ||
− | |||
− | |||
− | |||
<div class="highlight-c notranslate"> | <div class="highlight-c notranslate"> | ||
第300行: | 第241行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="c">pValue = PyObject_CallObject(pFunc, pArgs);</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 在函数返回时,<code>pValue</code> 要么是 <code>NULL</code>,要么包含对函数返回值的引用。 请务必在检查值后释放引用。 | |
− | |||
− | |||
第314行: | 第253行: | ||
<span id="extending-with-embedding"></span> | <span id="extending-with-embedding"></span> | ||
− | == | + | == 1.4. 扩展嵌入式 Python == |
− | + | 到目前为止,嵌入式 Python 解释器无法访问应用程序本身的功能。 Python API 通过扩展嵌入式解释器来实现这一点。 也就是说,嵌入式解释器使用应用程序提供的例程进行扩展。 虽然听起来很复杂,但它并没有那么糟糕。 暂时忘记应用程序启动 Python 解释器。 相反,将应用程序视为一组子例程,并编写一些胶水代码让 Python 能够访问这些例程,就像编写普通的 Python 扩展一样。 例如: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | Python | ||
<div class="highlight-c notranslate"> | <div class="highlight-c notranslate"> | ||
第329行: | 第261行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="c">static int numargs=0; |
/* Return the number of arguments of the application command line */ | /* Return the number of arguments of the application command line */ | ||
第335行: | 第267行: | ||
emb_numargs(PyObject *self, PyObject *args) | emb_numargs(PyObject *self, PyObject *args) | ||
{ | { | ||
− | if(!PyArg_ParseTuple(args, | + | if(!PyArg_ParseTuple(args, ":numargs")) |
return NULL; | return NULL; | ||
return PyLong_FromLong(numargs); | return PyLong_FromLong(numargs); | ||
第341行: | 第273行: | ||
static PyMethodDef EmbMethods[] = { | static PyMethodDef EmbMethods[] = { | ||
− | { | + | {"numargs", emb_numargs, METH_VARARGS, |
− | + | "Return the number of arguments received by the process."}, | |
{NULL, NULL, 0, NULL} | {NULL, NULL, 0, NULL} | ||
}; | }; | ||
static PyModuleDef EmbModule = { | static PyModuleDef EmbModule = { | ||
− | PyModuleDef_HEAD_INIT, | + | PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods, |
NULL, NULL, NULL, NULL | NULL, NULL, NULL, NULL | ||
}; | }; | ||
第354行: | 第286行: | ||
PyInit_emb(void) | PyInit_emb(void) | ||
{ | { | ||
− | return PyModule_Create(& | + | return PyModule_Create(&EmbModule); |
− | }</ | + | }</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 在 <code>main()</code> 函数的正上方插入上述代码。 此外,在调用 [[../../c-api/init#c|Py_Initialize()]] 之前插入以下两个语句: | |
− | |||
<div class="highlight-c notranslate"> | <div class="highlight-c notranslate"> | ||
第367行: | 第298行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="c">numargs = argc; |
− | PyImport_AppendInittab( | + | PyImport_AppendInittab("emb", &PyInit_emb);</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 这两行初始化 <code>numargs</code> 变量,并使嵌入式 Python 解释器可以访问 <code>emb.numargs()</code> 函数。 通过这些扩展,Python 脚本可以执行以下操作: | |
− | <code>emb.numargs()</code> | ||
− | |||
<div class="highlight-python notranslate"> | <div class="highlight-python notranslate"> | ||
第381行: | 第310行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python">import emb |
− | print( | + | print("Number of arguments", emb.numargs())</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 在实际应用程序中,这些方法会将应用程序的 API 公开给 Python。 | |
− | |||
第395行: | 第323行: | ||
<span id="embeddingincplusplus"></span> | <span id="embeddingincplusplus"></span> | ||
− | == | + | == 1.5. 在 C++ 中嵌入 Python == |
− | + | 也可以在 C++ 程序中嵌入 Python; 具体如何完成取决于所使用的 C++ 系统的细节; 通常,您需要用 C++ 编写主程序,并使用 C++ 编译器来编译和链接您的程序。 无需使用 C++ 重新编译 Python 本身。 | |
− | |||
− | |||
− | |||
第407行: | 第332行: | ||
<span id="compiling"></span> | <span id="compiling"></span> | ||
− | == | + | == 1.6. 在类 Unix 系统下编译和链接 == |
− | + | 找到传递给编译器(和链接器)以将 Python 解释器嵌入到应用程序中的正确标志并不一定是微不足道的,特别是因为 Python 需要加载实现为 C 动态扩展的库模块(<code>.so</code>文件)链接到它。 | |
− | |||
− | |||
− | |||
− | |||
− | + | 要找出所需的编译器和链接器标志,您可以执行作为安装过程的一部分生成的 <code>pythonX.Y-config</code> 脚本(也可以使用 <code>python3-config</code> 脚本)。 此脚本有多个选项,其中以下选项对您直接有用: | |
− | <code>pythonX.Y-config</code> | ||
− | |||
− | |||
− | |||
<ul> | <ul> | ||
− | <li><p><code>pythonX.Y-config --cflags</code> | + | <li><p><code>pythonX.Y-config --cflags</code> 会在编译时给你推荐的标志:</p> |
− | |||
<div class="highlight-shell-session notranslate"> | <div class="highlight-shell-session notranslate"> | ||
<div class="highlight"> | <div class="highlight"> | ||
− | <pre>$ /opt/bin/python3.4-config --cflags | + | <pre class="session">$ /opt/bin/python3.4-config --cflags |
-I/opt/include/python3.4m -I/opt/include/python3.4m -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes</pre> | -I/opt/include/python3.4m -I/opt/include/python3.4m -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes</pre> | ||
第434行: | 第350行: | ||
</div></li> | </div></li> | ||
− | <li><p><code>pythonX.Y-config --ldflags</code> | + | <li><p><code>pythonX.Y-config --ldflags</code> 会在链接时给你推荐的标志:</p> |
− | |||
<div class="highlight-shell-session notranslate"> | <div class="highlight-shell-session notranslate"> | ||
<div class="highlight"> | <div class="highlight"> | ||
− | <pre>$ /opt/bin/python3.4-config --ldflags | + | <pre class="session">$ /opt/bin/python3.4-config --ldflags |
-L/opt/lib/python3.4/config-3.4m -lpthread -ldl -lutil -lm -lpython3.4m -Xlinker -export-dynamic</pre> | -L/opt/lib/python3.4/config-3.4m -lpthread -ldl -lutil -lm -lpython3.4m -Xlinker -export-dynamic</pre> | ||
第449行: | 第364行: | ||
<div class="admonition note"> | <div class="admonition note"> | ||
− | + | 笔记 | |
− | + | 为避免多个 Python 安装之间的混淆(尤其是系统 Python 和您自己编译的 Python 之间),建议您使用 <code>pythonX.Y-config</code> 的绝对路径,如上例所示。 | |
− | |||
− | |||
− | |||
</div> | </div> | ||
− | + | 如果此过程对您不起作用(不保证适用于所有类 Unix 平台;但是,我们欢迎 [[../../bugs#reporting-bugs|错误报告]] ),您将必须阅读您的系统关于动态链接和/或检查 Python 的 <code>Makefile</code>(使用 [[../../library/sysconfig#sysconfig|sysconfig.get_makefile_filename()]] 找到它的位置)和编译选项。 在这种情况下,[[../../library/sysconfig#module-sysconfig|sysconfig]] 模块是一个有用的工具,可以以编程方式提取您想要组合在一起的配置值。 例如: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<div class="highlight-pycon notranslate"> | <div class="highlight-pycon notranslate"> | ||
第471行: | 第376行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="pycon">>>> import sysconfig |
− | + | >>> sysconfig.get_config_var('LIBS') | |
'-lpthread -ldl -lutil' | '-lpthread -ldl -lutil' | ||
− | + | >>> sysconfig.get_config_var('LINKFORSHARED') | |
− | '-Xlinker -export-dynamic'</ | + | '-Xlinker -export-dynamic'</syntaxhighlight> |
</div> | </div> | ||
第484行: | 第389行: | ||
</div> | </div> | ||
+ | <div class="clearer"> | ||
− | [[Category:Python 3.9 | + | |
+ | |||
+ | </div> | ||
+ | |||
+ | [[Category:Python 3.9 文档]] |
2021年10月31日 (日) 04:50的最新版本
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 对象。
1.1. 非常高级的嵌入
嵌入 Python 的最简单形式是使用非常高级的接口。 此接口旨在执行 Python 脚本,而无需直接与应用程序交互。 例如,这可用于对文件执行某些操作。
Py_SetProgramName() 函数应该在 Py_Initialize() 之前调用,以通知解释器有关 Python 运行时库的路径。 接下来,使用 Py_Initialize() 初始化 Python 解释器,然后执行打印日期和时间的硬编码 Python 脚本。 之后, Py_FinalizeEx() 调用关闭解释器,然后程序结束。 在实际程序中,您可能希望从其他来源(可能是文本编辑器例程、文件或数据库)获取 Python 脚本。 使用 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 脚本中的函数。 就像在关于非常高级接口的部分中一样,Python 解释器不直接与应用程序交互(但这将在下一节中更改)。
运行 Python 脚本中定义的函数的代码是:
此代码使用 argv[1]
加载 Python 脚本,并调用 argv[2]
中命名的函数。 它的整数参数是 argv
数组的其他值。 如果你编译并链接这个程序(我们调用完成的可执行文件call),并用它来执行一个Python脚本,例如:
那么结果应该是:
$ call multiply multiply 3 2
Will compute 3 times 2
Result of call: 6
尽管该程序的功能相当庞大,但大部分代码用于 Python 和 C 之间的数据转换以及错误报告。 关于嵌入 Python 的有趣部分始于
初始化解释器后,使用 PyImport_Import() 加载脚本。 该例程需要一个 Python 字符串作为其参数,该字符串是使用 PyUnicode_FromString() 数据转换例程构造的。
加载脚本后,我们将使用 PyObject_GetAttrString() 检索我们要查找的名称。 如果名称存在,并且返回的对象是可调用的,则可以安全地假设它是一个函数。 然后程序继续像往常一样构造一个参数元组。 然后使用以下命令调用 Python 函数:
在函数返回时,pValue
要么是 NULL
,要么包含对函数返回值的引用。 请务必在检查值后释放引用。
1.4. 扩展嵌入式 Python
到目前为止,嵌入式 Python 解释器无法访问应用程序本身的功能。 Python API 通过扩展嵌入式解释器来实现这一点。 也就是说,嵌入式解释器使用应用程序提供的例程进行扩展。 虽然听起来很复杂,但它并没有那么糟糕。 暂时忘记应用程序启动 Python 解释器。 相反,将应用程序视为一组子例程,并编写一些胶水代码让 Python 能够访问这些例程,就像编写普通的 Python 扩展一样。 例如:
在 main()
函数的正上方插入上述代码。 此外,在调用 Py_Initialize() 之前插入以下两个语句:
这两行初始化 numargs
变量,并使嵌入式 Python 解释器可以访问 emb.numargs()
函数。 通过这些扩展,Python 脚本可以执行以下操作:
在实际应用程序中,这些方法会将应用程序的 API 公开给 Python。
1.5. 在 C++ 中嵌入 Python
也可以在 C++ 程序中嵌入 Python; 具体如何完成取决于所使用的 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
的绝对路径,如上例所示。
如果此过程对您不起作用(不保证适用于所有类 Unix 平台;但是,我们欢迎 错误报告 ),您将必须阅读您的系统关于动态链接和/或检查 Python 的 Makefile
(使用 sysconfig.get_makefile_filename() 找到它的位置)和编译选项。 在这种情况下,sysconfig 模块是一个有用的工具,可以以编程方式提取您想要组合在一起的配置值。 例如: