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

Python.h找不到或编译报错
多数人在第一次尝试嵌入Python时卡在这步:g++ 编译时报 fatal Error: Python.h: No such file or Directory。这不是代码问题,而是开发头文件没装全。
linux下必须安装对应Python版本的-dev包,比如用Python 3.9就装 python3.9-dev(ubuntu/debian)或 python39-devel(centos/RHEL)。macos用Homebrew装Python时默认带头文件,但如果你用pyenv或手动编译过Python,得确认 python3-config --includes 输出路径是否被编译器识别。
- 检查头文件是否存在:
ls /usr/include/python3.9/Python.h或python3.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最难的从来不是语法,而是路径、生命周期和线程边界这三件事。漏掉任意一个,程序可能当场静默崩溃,也可能跑几天才出问题。