python.h头文件和libpython动态库必须提前配置好,否则无法编译;ubuntu需装python3-dev,macos用brew install python并指定-i/-l路径,windows须匹配msvc版本;验证方式为编写调用py_initialize()的最小main函数并成功运行。

Python.h 依赖和编译环境必须提前配好
直接调用 Python C API 前,Python.h 头文件和对应的动态库(如 libpython3.9.so 或 python39.lib)必须可用,否则连编译都过不去。Windows 下容易漏掉 Py_SetPythonHome,Linux/macOS 则常因未链接 -lpython3.9 报 undefined reference to Py_Initialize。
验证方式很简单:写个最小 main() 调用 Py_Initialize(),能编译+运行不崩溃,才算环境搭对了。
- Ubuntu/Debian:装
python3-dev包,不是只装python3 - macOS:用
brew install python后,头文件在/opt/homebrew/include/python3.11这类路径,编译时得加-I和-L - Windows:务必用和 Python 完全同版本的 MSVC 编译器(比如 Python 3.9.13 是用 VS2019 编译的),混用会导致
PyUnicode_AsUTF8崩溃
用 PyRun_SimpleStringFlags 执行带参数的脚本最轻量
如果只是“跑一次脚本、传几个字符串参数、拿个输出文本”,别急着上 PyImport_ImportModule + PyObject_CallObject。用 PyRun_SimpleStringFlags 注入 sys.argv 更直接。
关键点是:C++ 里改 sys.argv 必须在 Py_Initialize() 之后、脚本执行之前,且要确保 argv[0] 是脚本路径(哪怕假的),否则 Python 解释器会报 ValueError: not enough values to unpack。
Py_SetProgramName(argv[0]); // 必须设,否则 argv[0] 可能为空
Py_Initialize();
PySys_SetArgvEx(3, const_cast<char>(argv), 0); // argv = {"fake.py", "arg1", "arg2"}
PyRun_SimpleStringFlags("import sys; print('Received:', sys.argv[1:])", nullptr);
</char>
-
PySys_SetArgvEx第三个参数为0表示不复制字符串,所以argv数组生命周期必须长于 Python 执行过程 - 想捕获
print输出?重定向sys.stdout到io.StringIO,再用getattr拿.getvalue()—— 不要用freopen,跨线程不安全
需要结构化返回值就用 PyRun_String + PyObject_GetAttrString
当 Python 脚本要返回 dict/list/int 等原生对象(不只是打印文本),就得用 PyRun_String 执行表达式或模块级代码块,并显式获取返回值对象。
典型场景:Python 脚本末尾写 result = {"code": 0, "data": [1,2,3]},C++ 里通过模块名拿到这个 result 变量:
PyObject* pModule = PyImport_AddModule("__main__");
PyObject* pResult = PyObject_GetAttrString(pModule, "result");
// 然后用 PyLong_AsLong / PyDict_GetItemString / PyList_Size 等取值
- 不能用
PyImport_ImportModule("mymodule")再 import 脚本文件——那会重新加载,result在新命名空间里,旧的没更新 - 所有
PyObject*用完必须调Py_DECREF,漏掉就会内存泄漏;PyRun_String返回的对象引用计数为 1,必须手动释放 - 若脚本抛异常,
PyRun_String返回NULL,此时要调PyErr_Print()查错,否则后续调用全崩
多线程调用必须处理 GIL 和线程状态
如果 C++ 主程序是多线程的(比如 Qt 或 Web 服务后台),每次调 Python 前必须确保当前线程有有效的 Python 线程状态,否则 PyRun_SimpleStringFlags 直接 segfault。
最稳妥做法:每个工作线程首次调 Python 时,调 PyThreadState_New 创建专属状态,并用 PyThreadState_Swap 切换进去;退出前调 PyThreadState_Clear + PyThreadState_Delete。
- 千万别在子线程里反复调
Py_Initialize—— 它不是线程安全的,只允许调一次 - 如果只是短时调用(比如每秒几次),可以用
PyGILState_Ensure/PyGILState_Release包裹,但注意这不解决线程状态缺失问题,仅保证 GIL 被持有 - Python 3.12+ 默认启用自由线程(
--without-pymalloc配置除外),但 C API 层仍需手动管理线程状态,这点没变
C++ 调 Python 最容易卡在环境初始化和线程状态上,而不是语法本身;一旦 Py_Initialize 成功且单线程能跑通,后面就是查文档补类型转换细节的事。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











