C++如何调用Python脚本?(嵌入式Python接口示例)

1次阅读

python.h报错需装-dev包;py_initialize后须用py_setpath()设标准库路径;传参调用需类型转换和引用计数;线程需gil管理。

C++如何调用Python脚本?(嵌入式Python接口示例)

Python.h找不到或编译报错

多数人在第一次尝试嵌入Python时卡在这步:g++ 编译时报 fatal Error: Python.h: No such file or Directory。这不是代码问题,而是开发头文件没装全。

linux下必须安装对应Python版本的-dev包,比如用Python 3.9就装 python3.9-devubuntu/debian)或 python39-develcentos/RHEL)。macos用Homebrew装Python时默认带头文件,但如果你用pyenv或手动编译过Python,得确认 python3-config --includes 输出路径是否被编译器识别。

  • 检查头文件是否存在:ls /usr/include/python3.9/Python.hpython3.9-config --includes
  • 编译时显式加包含路径:g++ -I$(python3.9-config --includes) ...
  • 链接时别漏掉库路径和名字:-L$(python3.9-config --ldflags) -lpython3.9

Py_Initialize()后调用PyRun_SimpleString失败

常见现象是程序直接崩溃,或输出 ImportError: No module named 'encodings'。这是因为Python解释器初始化后找不到标准库路径,尤其在非标准安装或交叉编译环境下更明显。

必须在Py_Initialize()之后、任何Python API调用之前,用Py_SetPythonHome()Py_SetPath()明确告诉解释器“你的家在哪”。路径值不能靠猜——得用python3.9-config --exec-prefix--shared-libdir组合推导,或直接复用当前Python进程的实际路径:

立即学习Python免费学习笔记(深入)”;

  • 运行 python3.9 -c "import sys; print(sys.path)" 看实际路径列表
  • Py_SetPath()传入拼接好的char*,例如"/usr/lib/python3.9:/usr/lib/python3.9/lib-dynload"
  • Py_SetPythonHome()设为exec-prefix(如/usr),不是sys.prefix

c++传参给python脚本并获取返回值

直接用PyRun_SimpleString()只能执行语句,没法取返回值。真要交互,得走模块+函数调用流程:加载模块、获取函数对象、构造参数元组、调用、解析结果。

关键点在于类型转换必须严格——C++的int不能直接塞进Python调用,得用PyLong_FromLong()包装;字符串要用PyUnicode_FromString();返回的PyObject*也得用PyLong_AsLong()PyUnicode_AsUTF8()安全提取,否则空指针会崩。

  • 不要用PyRun_String()Py_eval_input来“模拟返回”,它不处理异常且难调试
  • 每次PyLong_FromXXX()后记得检查返回值是否为nullptr(Python API出错时返回空)
  • 用完PyObject*必须调Py_DECREF(),否则内存泄漏——嵌入场景下没GC兜底

多线程调用Python导致crash或死锁

Python解释器全局锁(GIL)会让多线程C++程序在调用Python API时意外串行,甚至在没调PyEval_InitThreads()(旧版)或没正确管理线程状态(新版)时直接段错误。

如果C++主线程初始化了解释器,其他工作线程又要调Python,必须每个线程都调PyThreadState_Swap()切换自己的PyThreadState*,并在退出前恢复。更稳妥的做法是:只在单个线程里跑Python,其余线程用消息队列把任务发过去执行。

  • 新版Python(3.12起)已移除PyEval_InitThreads(),改用PyEval_RestoreThread() + PyEval_SaveThread()配对
  • 调用Python前务必确保当前线程持有GIL:PyGILState_Ensure(),用完PyGILState_Release()
  • 若只是执行简单脚本且不涉及Python对象生命周期管理,优先考虑system()popen()启动子进程——省事还不用操心GIL

嵌入Python最难的从来不是语法,而是路径、生命周期和线程边界这三件事。漏掉任意一个,程序可能当场静默崩溃,也可能跑几天才出问题。

text=ZqhQzanResources