本文目录导读:

Python的C扩展API(通常称为Python/C API)一直以来都以“功能强大但学习曲线陡峭”而闻名,从整体趋势来看,它确实在变得更易用,但改善主要集中在特定方面,对于初学者来说,门槛依然不低。
要回答这个问题,需要区分“嵌入”(Embedding)和“扩展”(Extending),并看看具体的改进点。
对于“嵌入”(Embedding)而言:进步显著
所谓嵌入,是指在C/C++程序中启动并运行Python解释器,调用Python代码。
-
Python 3.12+ 的“稳定ABI”(Stable ABI)和“有限API”(Limited API)
- 过去的问题: 嵌入程序必须链接到特定版本Python的
python3.x库,如果用户系统上Python版本不同(例如代码用3.10编译,用户只有3.12),程序就会崩溃,这导致分发嵌入Python的大型C++应用(如游戏引擎、CAD软件)异常困难。 - 现在的改进: Python 3.11/3.12大幅强化了稳定ABI,通过使用
Py_LIMITED_API宏,嵌入代码可以只链接到通用的python3库,这意味着用Python 3.11编译的程序,可以在Python 3.12、3.13等系统上无缝运行。这大大降低了嵌入的部署和维护成本。
- 过去的问题: 嵌入程序必须链接到特定版本Python的
-
初始化变得更加简单
- Py_Initialize() 升级: 以前需要手动设置
PyImport_AppendInittab、配置PyConfig(一个非常庞大的结构体),现在Python 3.8+引入了Py_InitializeFromConfig(),推荐使用更清晰的PyConfig管理。 - 更清晰的错误处理: 新的API(如
pystatus)提供了更结构化的错误处理,而不是仅仅返回一个整数或NULL指针。
- Py_Initialize() 升级: 以前需要手动设置
-
GIL管理工具的改进
- 虽然GIL(全局解释器锁)本身未消失,但C API提供了更清晰的宏(如
PyGILState_Ensure()/PyGILState_Release()),使得在多线程C程序中安全地调用Python代码更不容易出错。
- 虽然GIL(全局解释器锁)本身未消失,但C API提供了更清晰的宏(如
对于“扩展”(Extending)而言:依然有门槛,但有好消息
扩展是指用C/C++编写模块,让Python调用。
-
PyModule_Create取代旧版: 模块定义结构体PyModuleDef比旧式的initmodule函数更清晰,但依然需要手动填写许多函数指针和槽位。 -
类型定义依然复杂: 创建一个自定义的Python类型(用C实现)仍然需要处理
PyTypeObject结构体,理解tp_new、tp_init、tp_dealloc、tp_members、tp_getset等大量槽位。这部分的学习曲线几乎没有变缓。 -
最大的利好: 对于多数场景,现在根本不需要直接使用C API了!!!
- pybind11 (C++): 这是目前最流行的方式,它基于C++11/14,用模板元编程封装了Python/C API的复杂性,定义类、方法、异常、自动类型转换都变得非常简洁。这是嵌入和扩展的首选方案。
- Cython: 一种类似Python的语言,可以编译成C扩展,它提供了
@cfunc、@ccall等装饰器,语法Pythonic,但能生成接近C的性能。 - Nanobind: pybind11的轻量级继任者,主要用于高性能场景,编译更快,更小。
- ctypes/cffi: 如果只是调用现有的C库,完全无需C API,用
ctypes或cffi直接在Python中加载.so/.dll文件即可。
| 方面 | 过去(Python 2/3.7以前) | Python 3.11+) | 是否更易用? |
|---|---|---|---|
| 嵌入 | 版本绑定严重,配置繁琐,调试困难 | 稳定ABI成熟,初始化流程清晰,错误处理改善 | 显著更易用 |
| 扩展(原始C API) | 依然需要手动填写大量结构体,内存管理需手动引用计数 | API细节略有简化,但核心复杂度未变 | 变化不大,依然很硬核 |
| 扩展(通过工具) | 选项有限(主要是SWIG或Boost.Python) | pybind11、Cython、Nanobind 极其成熟和易用 | 革命性提升 |
总结成一句话:
如果你必须用纯C API,尤其是扩展,它依然很难用,但如果你通过pybind11或Cython(推荐路线),或用C API做嵌入(受益于稳定ABI),那么用户体验已经大大改善了。
如果你想避免痛苦,建议:
- 嵌入Python到C++程序: 直接使用 pybind11 的
embed模块或原始C API(但注意使用Py_LIMITED_API)。 - 为Python写C/C++扩展: 首选pybind11,其次Cython,只有极特殊情况(如需要极底层的控制或嵌入到不支持C++的C程序)才考虑原始C API。
一个关于Python 3.14的预告: PEP 697(限制/简化某些类型槽位)和进一步的内部清理正在进行中,原始C API的复杂度有希望在后续版本中继续降低,但不会一蹴而就。