CPython 嵌入指南:在 C/C++ 应用中集成 Python 解释器的完整实践(基于 Doc/extending/embedding.rst) CPython 嵌入指南在 C/C 应用中集成 Python 解释器的完整实践基于 Doc/extending/embedding.rst【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本篇基于 CPython 官方文档Doc/extending/embedding.rst展开系统讲解如何将 Python 解释器嵌入到 C/C 应用中从最顶层的PyRun_SimpleString一行式调用到纯嵌入模式下的模块导入与函数调用再到反向扩展嵌入式解释器最后覆盖 Unix-like 系统下的编译链接要点。读完后你将能够在自己的 C/C 程序中初始化解释器、执行 Python 代码、交换数据并正确地完成构建。一、什么是嵌入与“扩展 Python”的本质区别CPython 文档在Doc/extending/embedding.rst开篇即点明两条方向相反的技术路径扩展extending给 Python 挂上一个 C 函数库。此时应用的主程序仍是 Python 解释器C 代码只是被调用的插件。嵌入embedding你的 C/C 应用才是主程序它在需要时“偶发地”调用 Python 解释器执行一些 Python 代码。嵌入让你可以用 Python 实现应用的一部分功能——典型场景是让用户通过 Python 脚本定制应用行为或者你自己发现某些逻辑用 Python 写起来更省事。两者的接口工作在内核层面是对称的。文档用三步对比说得很清楚阶段扩展 PythonC 被 Python 调用嵌入 PythonPython 被 C 调用1数据从 Python 转成 C数据从 C 转成 Python2调用 C 例程调用 Python 例程3数据从 C 转回 Python数据从 Python 转回 C可以看到数据转换步骤只是交换了方向唯一不同的只是中间那次跨语言的例程调用对象。因此扩展篇见Doc/extending/first-extension-module.rst、Doc/extending/extending.rst里关于引用计数与错误处理的规则在嵌入场景下同样适用。嵌入方必须自己提供main函数而main的首要职责就是初始化解释器至少调用一次Py_Initialize()或带配置的Py_InitializeFromConfig()此后应用中的任意位置都可以调用解释器。文档指出三种调用解释器的方式把一段包含 Python 语句的字符串传给PyRun_SimpleString()把一个 stdio 文件指针和文件名传给PyRun_SimpleFile()文件名仅用于错误信息中标识来源直接使用底层 API 构造和操作 Python 对象即后文“纯嵌入”所用的方式。二、Very High Level Embedding最顶层接口最简形态的嵌入是“执行一段 Python 脚本但不需要与应用直接交互”例如对某个文件执行某种处理。文档给出的完整示例如下源码语言为 C#define PY_SSIZE_T_CLEAN #include Python.h int main(int argc, char *argv[]) { PyStatus status; PyConfig config; PyConfig_InitPythonConfig(config); /* optional but recommended */ status PyConfig_SetBytesString(config, config.program_name, argv[0]); if (PyStatus_Exception(status)) { goto exception; } status Py_InitializeFromConfig(config); if (PyStatus_Exception(status)) { goto exception; } PyConfig_Clear(config); PyRun_SimpleString(from time import time,ctime\n print(Today is, ctime(time()))\n); if (Py_FinalizeEx() 0) { exit(120); } return 0; exception: PyConfig_Clear(config); Py_ExitStatusException(status); }这段代码的执行流程值得逐行理解构造默认配置PyConfig_InitPythonConfig(config)初始化一个填充了默认值的PyConfig结构。设置程序名PyConfig_SetBytesString(config, config.program_name, argv[0])在调用Py_InitializeFromConfig之前设置PyConfig.program_name目的是告诉解释器 Python 运行时库的路径。从源码结构看initconfig.h 中program_name属于 “Path configuration inputs” 分区是解释器推导sys.path、标准库位置等路径信息的输入之一。按配置初始化Py_InitializeFromConfig(config)完成解释器初始化。该函数在 pylifecycle.h 中声明为返回PyStatus的接口初始化失败时通过PyStatus_Exception(status)宏检查最终由Py_ExitStatusException(status)打印错误并退出声明见 pylifecycle.h。释放配置PyConfig_Clear(config)清掉配置中的宽字符串等由调用方持有的资源异常路径上同样需要调用。执行硬编码脚本PyRun_SimpleString打印当前日期时间。在真实程序中脚本来源可以是文本编辑器例程、文件、甚至数据库。收尾Py_FinalizeEx()关闭解释器返回负值时程序以 120 退出。关于宏的补充说明与原文档 note 一致示例开头的#define PY_SSIZE_T_CLEAN用于声明部分 API 使用Py_ssize_t而非int自 Python 3.13 起它已不再是必需的此处保留仅为向后兼容。PyRun_SimpleString与PyRun_SimpleFile的底层实现可以追溯到 pythonrun.h前者是PyRun_SimpleStringFlags(s, NULL)的宏封装后者等价于PyRun_SimpleFileExFlags(f, p, 0, NULL)。如果你需要执行的是文件中的脚本直接用PyRun_SimpleFile更合适——它替你省去了分配内存和读取文件内容的麻烦文件名字符串仅用于错误消息中标识来源。三、Beyond Very High Level为什么需要更低层接口顶层接口能让你执行任意 Python 代码但“交换数据值”相当麻烦。如果你需要在 C 应用与 Python 之间传递参数和返回值就得改用更低层的调用——代价是写更多 C 代码换来的是几乎无所不能的表达能力。四、Pure Embedding加载脚本并调用其中的函数文档的“纯嵌入”示例目标是执行某个 Python 脚本里定义的函数。完整代码见仓库中的 run-func.c核心逻辑如下节选并标注关键调用链#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 (Py_FinalizeEx() 0) { return 120; } return 0; }这个程序的用法是call pythonfile funcname [args]用argv[1]指定要加载的脚本argv[2]指定要调用的函数名其余的argv元素作为整型实参传入。关键调用链解析与原文档逐段对应初始化 导入脚本Py_Initialize(); pName PyUnicode_DecodeFSDefault(argv[1]); pModule PyImport_Import(pName);PyImport_Import需要的是一个 Python 字符串路径字符串经由数据转换例程PyUnicode_DecodeFSDefault构造而成。注意PyUnicode_DecodeFSDefault返回新引用导入完成后要Py_DECREF(pName)。取属性并检查可调用性pFunc PyObject_GetAttrString(pModule, argv[2]); /* pFunc is a new reference */ if (pFunc PyCallable_Check(pFunc)) { ... } Py_XDECREF(pFunc);脚本加载后用PyObject_GetAttrString按名字取出目标对象若它存在且PyCallable_Check为真才可以安全地把它当作函数调用。构造参数元组并调用pValue PyObject_CallObject(pFunc, pArgs);调用PyTuple_New新建元组后用PyTuple_SetItem填入每个PyLong_FromLong(atoi(...))转换出的参数——注意PyTuple_SetItem会“窃取”引用的所有权因此转换成功时不能再 DECREF 该对象。函数返回后pValue要么为NULL调用失败应PyErr_Print()打印异常要么持有函数返回值的新引用——检查完记得释放。验证运行效果把编译链接后的可执行文件命名为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 数据转换与错误报告代码这正是嵌入开发中“必须写、且容易写错”的部分。五、Extending Embedded Python反向扩展嵌入式解释器到目前为止嵌入式解释器无法访问宿主应用自身的功能。但 Python API 允许你反过来扩展嵌入式解释器——把它当作一个普通的扩展开发问题暂时忘掉“应用启动了解释器”这件事把应用看成一堆子例程再写胶水代码把其中某些例程暴露给 Python就像写一个常规的 Python 扩展模块一样。文档给出的示例代码static int numargs0; /* 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 emb_module_methods[] { {numargs, emb_numargs, METH_VARARGS, Return the number of arguments received by the process.}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef emb_module { .m_base PyModuleDef_HEAD_INIT, .m_name emb, .m_size 0, .m_methods emb_module_methods, }; static PyObject* PyInit_emb(void) { return PyModuleDef_Init(emb_module); }把上面的代码插入到main函数正上方再在Py_Initialize调用之前插入两行numargs argc; PyImport_AppendInittab(emb, PyInit_emb);这两行分别初始化numargs变量、并把emb.numargs函数注册进解释器的初始化表中使其对嵌入式解释器可见。于是 Python 脚本中就可以写import emb print(Number of arguments, emb.numargs())在真实应用里这些方法将把应用自身的 API 暴露给 Python——这正是“嵌入 反向扩展”组合的完整闭环C 程序启动解释器、执行 Python 脚本脚本又能回调 C 应用提供的能力。六、在 C 中嵌入 PythonPython 同样可以嵌入到 C 程序里具体做法取决于所用 C 工具链的细节一般原则是主程序用 C 编写用 C 编译器完成编译和链接即可没有必要用 C 重新编译 Python 本身。七、Unix-like 系统下的编译与链接找到嵌入解释器所需的编译器/链接器参数并不总是显而易见尤其考虑到 Python 还要加载以 C 动态扩展.so文件形式实现、且与该解释器一起链接的库模块。文档给出的标准做法是运行安装过程生成的python{X.Y}-config脚本也可能提供python3-config。该脚本的完整实现可参考仓库中的 python-config.in。从源码看--cflags由 “-I include 路径 编译期 CFLAGS” 拼接而成--ldflags --embed则在库参数前加入-lpython{版本}{abiflags}源码逻辑libs.append(-lpython pyver sys.abiflags)见 python-config.in并在非共享库构建时补上-L{LIBPL}。它对嵌入开发直接有用的选项编译期标志pythonX.Y-config --cflags$ /opt/bin/python3.11-config --cflags -I/opt/include/python3.11 -I/opt/include/python3.11 -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall链接期标志pythonX.Y-config --ldflags --embed$ /opt/bin/python3.11-config --ldflags --embed -L/opt/lib/python3.11/config-3.11-x86_64-linux-gnu -L/opt/lib -lpython3.11 -lpthread -ldl -lutil -lm注意为避免多个 Python 安装之间尤其是系统 Python 与自己编译的 Python 之间发生混淆推荐像上面示例那样使用python{X.Y}-config的绝对路径。该方案不奏效时的后备手段pythonX.Y-config并非保证在所有类 Unix 平台上都能工作。此时你需要阅读本系统关于动态链接的文档或检查 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其中LINKFORSHARED-Xlinker -export-dynamic正是嵌入场景的关键项它把可执行文件的符号表导出使得后续动态加载的 C 扩展.so能够解析到解释器及宿主程序中的符号。八、嵌入开发的要点清单综合原文档与仓库源码把嵌入 Python 的实践要点收敛如下初始化自己提供main至少调用Py_Initialize()如需控制路径推导优先使用PyConfig_InitPythonConfigPyConfig_SetBytesString(config, config.program_name, argv[0])Py_InitializeFromConfig的 PEP 587 风格初始化并妥善处理PyStatus异常Py_ExitStatusException异常路径记得PyConfig_Clear。执行方式分层不交换数据 →PyRun_SimpleString/PyRun_SimpleFile需要参数与返回值 →PyImport_ImportPyObject_GetAttrStringPyObject_CallObject的纯嵌入路径参考 run-func.c。引用纪律PyObject_GetAttrString、PyTuple_New、PyObject_CallObject返回的都是新引用PyTuple_SetItem会窃取引用——run-func.c 中的每一处Py_DECREF/Py_XDECREF都是这条规则的实例。反向扩展把宿主应用的 API 以普通扩展模块的方式注册PyImport_AppendInittab必须在Py_Initialize之前调用让脚本能import emb回调 C 代码。构建用python{X.Y}-config --cflags和--ldflags --embed获取编译链接参数失效时回退到sysconfig查询LIBS、LINKFORSHARED等变量C 宿主程序用 C 编译器编译链接即可无需重新编译 Python。原文档末尾还留有TODO: threads的注记说明多线程嵌入与错误处理边界是文档作者自认尚未充分覆盖的领域在多线程场景下嵌入解释器前建议自行查阅Doc/c-api/下关于 GIL 与生命周期 API如 pylifecycle.h的说明并以当前仓库源码为准。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

最新新闻

NAS部署music-scraper实现音乐库自动化管理

NAS部署music-scraper实现音乐库自动化管理

1. 项目概述作为一名音乐发烧友兼NAS重度用户,我最近终于解决了困扰多年的音乐库管理难题。通过在内网NAS上部署music-scraper这个音乐刮削工具,实现了对散落在各处的音乐文件的自动化整理和元数据补充。这个方案最吸引我的地方在于它整合了多个主流音乐…

2026/9/7 18:18:56
FanControl V273 风扇控制完整指南:3 步接管每一台风扇

FanControl V273 风扇控制完整指南:3 步接管每一台风扇

FanControl V273 风扇控制完整指南:3 步接管每一台风扇 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/f…

2026/9/7 18:18:56
猫抓插件完整教程:快速上手浏览器资源嗅探,免费下载网页视频与M3U8流媒体

猫抓插件完整教程:快速上手浏览器资源嗅探,免费下载网页视频与M3U8流媒体

猫抓插件完整教程:快速上手浏览器资源嗅探,免费下载网页视频与M3U8流媒体 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 猫…

2026/9/7 18:18:56
开源法律大模型私有化部署:企业法务智能化实操指南

开源法律大模型私有化部署:企业法务智能化实操指南

开源法律大模型私有化部署:企业法务智能化实操指南 【免费下载链接】Awesome-Chinese-LLM 整理开源的中文大语言模型,以规模较小、可私有化部署、训练成本较低的模型为主,包括底座模型,垂直领域微调及应用,数据集与教程…

2026/9/7 18:18:56
qwen-vl-utils 避坑实战:3 个函数搞定高分辨率图片与长视频的 Token 爆炸

qwen-vl-utils 避坑实战:3 个函数搞定高分辨率图片与长视频的 Token 爆炸

qwen-vl-utils 避坑实战:3 个函数搞定高分辨率图片与长视频的 Token 爆炸 【免费下载链接】Qwen3-VL Qwen3-VL is the multimodal large language model series developed by Qwen team, Alibaba Cloud. 项目地址: https://gitcode.com/GitHub_Trending/qw/Qwen3-…

2026/9/7 18:18:56
Redis Key数量爆炸?Shell脚本+DeepSeek自动化清理与TTL补设实战

Redis Key数量爆炸?Shell脚本+DeepSeek自动化清理与TTL补设实战

上周我把一台Redis实例的key数量从680万压到了180万。这个数字放在大厂可能不值一提,但在我们这种核心业务全挤在一台8G内存实例上的场景里,680万个key已经快到临界点了——每次执行一条慢命令,CPU就往上蹿,业务方在群里问“缓存服…

2026/9/7 18:13:56