直接用system()调用Python脚本不佳,因启动新进程、无法共享内存、参数受限、错误难捕获且崩溃不可感知;应嵌入Python解释器,注意设置PythonHome、线程初始化和sys.path。

为什么直接用 system() 调用 Python 脚本通常不是好主意
它启动新进程、无法共享内存、参数传递受限、错误捕获困难,且子进程崩溃时 C++ 主程序难以感知。真正需要混合编程时,得让 Python 解释器在 C++ 进程内运行——也就是“嵌入解释器”。
初始化 Python 解释器前必须处理的三件事
嵌入失败往往不是代码写错,而是环境没对齐:
-
Py_Initialize()前需确保Py_SetPythonHome()指向正确的 Python 安装路径(尤其当系统有多个 Python 版本或使用虚拟环境时) - 必须调用
PyEval_InitThreads()(Python 3.7+ 已废弃,但 3.6 及更早仍需显式调用;若用 3.7+,需确认 GIL 状态,建议用PyEval_SaveThread()/PyEval_RestoreThread()配合线程切换) - Python 的
sys.path默认不包含当前目录,执行脚本前要用PyRun_SimpleString("import sys; sys.path.insert(0, '.');")补上,否则import会报ModuleNotFoundError
PyRun_SimpleFile() 和 PyRun_String() 的适用场景差异
前者适合运行完整 .py 文件,后者适合动态拼接并执行 Python 代码片段:
-
PyRun_SimpleFile()接收FILE*,需先用fopen("script.py", "r")打开,注意文件编码(推荐 UTF-8 无 BOM),且不能传入参数 -
PyRun_String("print('hello')", Py_file_input, globals, locals)中的Py_file_input表示整段作为脚本执行;若要执行表达式(如"2 + 3"),改用Py_eval_input,返回值是PyObject*,可用PyLong_AsLong()提取结果 - 两个函数都忽略 Python 异常——出错时只打印 traceback 到 stderr,不抛异常给 C++。真要捕获错误,得用
PyRun_StringFlags()+PyErr_Occurred()+PyErr_Print()
从 Python 函数获取返回值并转成 C++ 类型的典型流程
比如 Python 脚本里定义了 def calc(a, b): return a * b + 1,C++ 中调用它并拿到 int 结果:
立即学习“Python免费学习笔记(深入)”;
PyObject* pModule = PyImport_ImportModule("mymath");
PyObject* pFunc = PyObject_GetAttrString(pModule, "calc");
PyObject* pArgs = PyTuple_New(2);
PyTuple_SetItem(pArgs, 0, PyLong_FromLong(5));
PyTuple_SetItem(pArgs, 1, PyLong_FromLong(7));
PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
long value = PyLong_AsLong(pResult); // 注意:pResult 为 NULL 时这里会 crash
Py_DECREF(pResult);
Py_DECREF(pArgs);
Py_DECREF(pFunc);
Py_DECREF(pModule);
关键点:每一步都要检查 NULL(尤其是 PyImport_ImportModule、PyObject_GetAttrString、PyObject_CallObject),漏掉一个就可能 segfault;所有 PyXXX_New 创建的对象,用完必须 Py_DECREF,否则内存泄漏。
最易被忽略的是 Python 对象引用计数规则和 GIL 占用时机——多线程环境下,每次进入 Python C API 前最好确认持有 GIL,离开前释放;否则看似正常的代码,在并发调用时会随机崩。











