python c api最轻量但需手动管理引用计数;pybind11是c++绑定事实标准;ctypes适合已编译动态库的跨语言调用;多线程下必须显式管理gil。

Python C API 直接调用最轻量,但得手动管理引用计数
想在 C++ 里跑 Python 代码,又不想引入第三方库?PyRun_SimpleString 和 PyEval_EvalCode 是最底层也最直接的入口。但别急着写逻辑——Python 对象生命周期全靠引用计数管,C++ 侧漏掉一次 Py_DECREF 就可能内存泄漏或段错误。
常见错误现象:Segmentation fault (core dumped) 出现在第二次调用后;或者 PyImport_ImportModule 返回 nullptr 却没检查 PyErr_Occurred(),导致后续操作崩溃。
- 必须在
main()开头调用Py_Initialize(),结尾调用Py_FinalizeEx()(注意不是Py_Finalize(),后者已弃用) - 所有从 Python API 拿到的
PyObject*,只要不是明确标注“borrowed reference”,就得自己Py_INCREF/Py_DECREF - 字符串传入要用
PyUnicode_FromString,别直接传 C 字符串指针给PyRun_SimpleString
pybind11 是目前最稳的 C++ 绑定方案,适合导出类和函数
如果你要让 Python 调用 C++ 类、方法,或者反过来把 Python 对象传进 C++ 做处理,pybind11 是当前事实标准。它不依赖 Python 解释器源码,头文件即用,编译快,ABI 兼容性比 Boost.Python 好太多。
使用场景:封装一个 C++ 算法模块供 Python 脚本批量调用;把 C++ 的 std::vector 自动转成 Python list;暴露带默认参数、重载的函数。
立即学习“Python免费学习笔记(深入)”;
- 导出函数时,
py::arg("x").noconvert()可禁用隐式类型转换,避免意外的int → float转换引发精度问题 - C++ 构造函数参数含非拷贝类型(如
std::unique_ptr),需显式用py::return_value_policy::reference_internal控制返回策略 - Windows 下链接失败报
LNK2001: unresolved external symbol __imp__Py...?确认是否用了与 Python 版本匹配的pybind11(比如 Python 3.11 需 pybind11 ≥ 2.10)
ctypes + .so/.dll 是跨语言胶水最灵活的方式,但得自己写 Python 侧胶水代码
如果你的 C++ 代码已经编译成动态库(Linux 下 .so,Windows 下 .dll),且只暴露 C 风格接口(extern "C"),那 Python 侧用 ctypes 加载是最轻量、无构建依赖的方案。
性能影响:相比 pybind11,ctypes 没有模板展开开销,但每次调用都要做类型转换和 ABI 适配,高频小函数调用会有明显额外开销。
- C++ 接口必须用
extern "C"包裹,否则 C++ 名字修饰(name mangling)会让ctypes.CDLL找不到符号 - Python 传
str给 C++,得先用.encode('utf-8')转成bytes,再用ctypes.c_char_p包装 - 返回
char*给 Python 时,如果内存由 C++ 分配,Python 侧必须用ctypes.create_string_buffer或手动free,否则内存泄漏
混合调试时最常卡在 Python 解释器状态和线程 GIL
C++ 调 Python 后程序卡死、多线程下结果错乱、或者 PyGILState_Ensure 报 SystemError: null argument to internal routine——八成是 GIL 搞的鬼。
根本原因:CPython 的全局解释器锁(GIL)不是自动随线程进入/退出的。C++ 新线程里调 Python API 前,必须显式获取 GIL;长时间纯计算任务中,应主动释放 GIL 让其他 Python 线程跑起来。
- 新线程首次调 Python 前,必须调
PyGILState_Ensure();退出前对应调PyGILState_Release() - 在 C++ 里做密集计算前,用
Py_BEGIN_ALLOW_THREADS释放 GIL;计算完用Py_END_ALLOW_THREADS恢复 -
PyRun_SimpleString这类函数内部会自动处理 GIL,但你封装的自定义 C++ 回调函数不会——只要回调里调了 Python API,就得自己管 GIL
复杂点在于:GIL 状态和 Python 解释器初始化状态是两个独立维度。哪怕 Py_IsInitialized() 返回 true,也不能假设当前线程已有 GIL。这两件事,一个都不能少。











