CPython C API 移除预告:Doc/deprecations 中“Pending Removal”弃用 API 全解与替代接口迁移指南
本文以 CPython 官方弃用文档 c-api-pending-removal-in-future.rst 为主体,逐一讲解其中列出的 C API 废弃项——异常处理三件套(PyErr_Fetch/PyErr_Restore/PyErr_NormalizeException)、线程本地存储 TLS 系列、切片、字符串、模块、fork 处理及 ob_shash 成员等——并对照当前仓库头文件源码,给出每一项的替代 API、弃用标记(Py_DEPRECATED 版本)与迁移依据。读完后,你能够为 C 扩展项目建立一套“废弃 API 排查 + 迁移到新接口”的完整操作路径。
1. 文档定位:什么是“Pending Removal”档
CPython 的 Doc/deprecations/ 目录维护着按移除节奏分类的弃用清单。本篇对应的 c-api-pending-removal-in-future.rst 收录的是已声明废弃、但尚未确定具体移除版本的 C API:
The following APIs are deprecated and will be removed, although there is currently no date scheduled for their removal.
这意味着这些接口当前仍可用,编译器通常只会通过 Py_DEPRECATED 宏产生弃用警告(而非编译错误),但它们已在 CPython 官方口径中列入移除名单。与“计划在某一具体版本移除”的弃用项不同,这一档的开发者应当把迁移视为确定性任务提前完成,而不是等待版本号倒逼。
原文档给出的全部条目如下,后文逐一结合源码展开:
| 废弃项 | 替代方案(原文档口径) |
|---|---|
Py_TPFLAGS_HAVE_FINALIZE |
Python 3.8 起不再需要 |
PyErr_Fetch |
PyErr_GetRaisedException |
PyErr_NormalizeException |
PyErr_GetRaisedException |
PyErr_Restore |
PyErr_SetRaisedException |
PyModule_GetFilename |
PyModule_GetFilenameObject |
PyOS_AfterFork |
PyOS_AfterFork_Child |
PySlice_GetIndicesEx |
PySlice_Unpack + PySlice_AdjustIndices |
PyUnicode_READY |
Python 3.12 起不再需要 |
PyErr_Display |
PyErr_DisplayException |
_PyErr_ChainExceptions |
_PyErr_ChainExceptions1 |
PyBytesObject.ob_shash 成员 |
改用 PyObject_Hash |
TLS 系列:PyThread_create_key / PyThread_delete_key / PyThread_set_key_value / PyThread_get_key_value / PyThread_delete_key_value |
对应 PyThread_tss_alloc / PyThread_tss_free / PyThread_tss_set / PyThread_tss_get / PyThread_tss_delete |
PyThread_ReInitTLS |
Python 3.7 起不再需要 |
2. 异常处理 API:从“三元组”到“单个异常对象”
2.1 被弃用的旧接口:PyErr_Fetch / PyErr_Restore / PyErr_NormalizeException
旧式异常存取基于“异常类型 + 值 + 回溯”三元组(exc, val, tb):先调用 PyErr_Fetch 取出三个引用并清空全局错误指示,暂存处理后用 PyErr_Restore 恢复,若拿到的不是实例而是类则用 PyErr_NormalizeException 归一化。这三个声明至今仍保留在 pyerrors.h 中:
PyAPI_FUNC(void) PyErr_Fetch(PyObject **, PyObject **, PyObject **); // L17
PyAPI_FUNC(void) PyErr_Restore(PyObject *, PyObject *, PyObject *); // L18
PyAPI_FUNC(void) PyErr_NormalizeException(PyObject**, PyObject**, PyObject**); // L40
其问题在于:调用方要自行管理三个引用的生命周期、处理“拿到类而非实例”的归一化分支,且取回后立即清空错误指示——一旦中途返回忘恢复,异常就静默丢失。
2.2 新接口:PyErr_GetRaisedException / PyErr_SetRaisedException
替代接口在同一头文件 pyerrors.h 中声明,围绕“单个已实例化的异常对象”操作:
PyAPI_FUNC(PyObject *) PyErr_GetRaisedException(void); // 取走当前异常(返回借用/移交引用的实例)
PyAPI_FUNC(void) PyErr_SetRaisedException(PyObject *); // 放回一个异常实例
迁移时的对应关系:PyErr_Fetch + PyErr_NormalizeException 两段式操作可合并为一次 PyErr_GetRaisedException 调用(返回的已是归一化后的实例);PyErr_Restore 改为 PyErr_SetRaisedException。CPython 3.11 起 PyErr_Fetch 本身在实现层面就是先取异常实例再拆三元组的兼容封装,从源码结构看,旧接口只是新接口的语法糖,迁移后行为语义保持一致。
2.3 同族弃用项:PyErr_Display 与内部 _PyErr_ChainExceptions
PyErr_Display(声明见 pythonrun.h)用于向文件对象打印三元组异常,弃用文档指定改用PyErr_DisplayException(见 pythonrun.h),同样只接收单个异常对象。_PyErr_ChainExceptions属于内部 API(原档中以!标记,未进入公开文档),其替代_PyErr_ChainExceptions1亦为内部接口;相关内部声明集中在 pycore_pyerrors.h。外部扩展一般不需要触碰这一对接口,但阅读解释器源码时会看到_PyErr_Fetch等内部变体仍保留在 pycore_pyerrors.h。
3. 线程本地存储:TLS 旧 API 全面让位于 TSS(PEP 539)
原文档列出的 6 个 TLS 接口在 pythread.h 中集中标注为弃用,且头部注释直接点明原因:
/* Thread Local Storage (TLS) API
TLS API is DEPRECATED. Use Thread Specific Storage (TSS) API.
The existing TLS API has used int to represent TLS keys across all
platforms, but it is not POSIX-compliant. Therefore, the new TSS API uses
opaque data type to represent TSS keys to be compatible (see PEP 539).
*/
Py_DEPRECATED(3.7) PyAPI_FUNC(int) PyThread_create_key(void);
Py_DEPRECATED(3.7) PyAPI_FUNC(void) PyThread_delete_key(int key);
Py_DEPRECATED(3.7) PyAPI_FUNC(int) PyThread_set_key_value(int key, void *value);
Py_DEPRECATED(3.7) PyAPI_FUNC(void *) PyThread_get_key_value(int key);
Py_DEPRECATED(3.7) PyAPI_FUNC(void) PyThread_delete_key_value(int key);
/* Cleanup after a fork */
Py_DEPRECATED(3.7) PyAPI_FUNC(void) PyThread_ReInitTLS(void);
关键事实有两条:其一,旧 TLS 用 int 表示键,在部分平台上不符合 POSIX 语义,这正是引入新接口的动机;其二,全部六个函数统一携带 Py_DEPRECATED(3.7) 标记,即编译时自 3.7 起即会触发弃用诊断。
替代的 TSS API 声明紧随其后(pythread.h),键类型为不透明的 Py_tss_t *:
typedef struct _Py_tss_t Py_tss_t; /* opaque */
PyAPI_FUNC(Py_tss_t *) PyThread_tss_alloc(void);
PyAPI_FUNC(void) PyThread_tss_free(Py_tss_t *key);
PyAPI_FUNC(int) PyThread_tss_is_created(Py_tss_t *key);
PyAPI_FUNC(int) PyThread_tss_create(Py_tss_t *key);
PyAPI_FUNC(void) PyThread_tss_delete(Py_tss_t *key);
PyAPI_FUNC(int) PyThread_tss_set(Py_tss_t *key, void *value);
PyAPI_FUNC(void *) PyThread_tss_get(Py_tss_t *key);
新旧接口一一对照(按原档映射):PyThread_create_key → PyThread_tss_alloc(配合 PyThread_tss_create 使用);PyThread_delete_key → PyThread_tss_free;PyThread_set_key_value → PyThread_tss_set;PyThread_get_key_value → PyThread_tss_get;PyThread_delete_key_value → PyThread_tss_delete。而 PyThread_ReInitTLS(fork 后重建 TLS)则因 CPython 3.7 起的 fork 处理方式变化而完全不再需要,无对应替代调用。
4. 切片:PySlice_GetIndicesEx 拆分为 Unpack + Adjust
被弃用的 PySlice_GetIndicesEx 声明带有 Py_DEPRECATED(3.7):
Py_DEPRECATED(3.7)
PyAPI_FUNC(int) PySlice_GetIndicesEx(PyObject *r, Py_ssize_t length,
Py_ssize_t *start, Py_ssize_t *stop,
Py_ssize_t *step,
Py_ssize_t *slicelength);
原档指定的替代是把一次调用拆成两步:PySlice_Unpack 负责从 slice 对象解析出 start/stop/step 三个整数,PySlice_AdjustIndices 负责按序列长度调整边界并返回切片长度(声明见 sliceobject.h)。值得注意的一个细节是:当前头文件里 PySlice_GetIndicesEx 已经以宏形式直接展开为新接口的组合调用(sliceobject.h),也就是说即使继续写旧函数名,实际执行的也已是 Unpack + AdjustIndices 路径——这从源码结构上印证了两套接口语义等价,迁移是纯机械替换。
5. 字符串与对象模型的“不再需要”项
5.1 PyUnicode_READY:PEP 393 之后的空操作
该宏曾用于把旧式字符串对象转换为宽字符表示。在当前仓库中它已实现为固定返回 0 的 static inline 桩函数(unicodeobject.h):
/* For backward compatibility. Soft-deprecated. */
static inline int PyUnicode_READY(PyObject* Py_UNUSED(op))
{
return 0;
}
#define PyUnicode_READY(op) PyUnicode_READY(_PyObject_CAST(op))
自 3.12 起 str 对象内部只保留 PEP 393 的 compact/legacy 布局,不存在“需要就绪转换”的中间态,因此该调用可直接从扩展代码中删除,无需任何替代调用。
5.2 Py_TPFLAGS_HAVE_FINALIZE:3.8 起的冗余标志位
该类型标志定义在 object.h:
#define Py_TPFLAGS_HAVE_FINALIZE (1UL << 0)
它对应 tp_flags 的第 0 位,历史上用于声明类型支持 tp_finalize。由于 CPython 3.8 起 tp_finalize 成为所有堆类型的默认能力,设置该标志不再产生任何行为差异,可以在类型定义里直接删掉这一位。
6. 模块文件名、fork 通知与 bytes 哈希缓存
6.1 PyModule_GetFilename → PyModule_GetFilenameObject
旧接口返回 const char *,声明处标注 Py_DEPRECATED(3.2)(moduleobject.h);新接口 PyModule_GetFilenameObject(moduleobject.h)返回 PyObject * 字符串对象。核心区别在编码处理:const char * 受文件系统编码约束且生命周期绑定内部缓存,字符串对象则由调用方按引用计数管理,语义更清晰。迁移时注意新接口返回值需要释放引用。
6.2 PyOS_AfterFork → PyOS_AfterFork_Child
声明位于 intrcheck.h:
PyAPI_FUNC(void) PyOS_AfterFork_Parent(void);
PyAPI_FUNC(void) PyOS_AfterFork_Child(void);
/* Deprecated, please use PyOS_AfterFork_Child() instead */
Py_DEPRECATED(3.7) PyAPI_FUNC(void) PyOS_AfterFork(void);
旧函数在 fork 后的父子进程中行为相同,新 API 拆分为 _Parent / _Child 两个精确入口。嵌入 Python 的解释器宿主在 fork() 之后应按所在进程调用对应函数,PyOS_AfterFork 的 Py_DEPRECATED(3.7) 标记同样意味着长期编译警告。
6.3 PyBytesObject.ob_shash 成员 → PyObject_Hash
bytes 对象结构体中的哈希缓存字段已在 cpython/bytesobject.h 标注弃用:
Py_DEPRECATED(3.11) Py_hash_t ob_shash;
扩展代码不应直接读取该内部字段做“命中则跳过计算”的优化,而应统一调用 PyObject_Hash——哈希缓存由运行时在正确的位置维护,直接读字段属于越界访问内部实现,且该字段随 API 清理随时可能被移除。
7. 实操建议:如何在自己的 C 扩展中排查这些废弃调用
- 打开弃用诊断:
Py_DEPRECATED宏最终映射到编译器的 deprecated 属性。用-Wall -Wdeprecated-declarations(MSVC 对应/W4下的 C4996)重新编译扩展,本清单中的接口会逐一报警告。 - 按头文件快速核对:上表所列声明均可在当前仓库直接查证——异常处理见 Include/pyerrors.h,线程存储见 Include/pythread.h,切片见 Include/sliceobject.h,模块见 Include/moduleobject.h,fork 见 Include/intrcheck.h,字符串与类型标志见 Include/cpython/unicodeobject.h 与 Include/object.h,bytes 字段见 Include/cpython/bytesobject.h。
- 区分“换接口”与“直接删除”两类迁移:
PyErr_Fetch/Restore、TLS 系列、PySlice_GetIndicesEx、PyModule_GetFilename、PyOS_AfterFork属于有替代接口的替换;而PyUnicode_READY、PyThread_ReInitTLS、Py_TPFLAGS_HAVE_FINALIZE属于能力内建化后的冗余项,正确迁移是删除调用/标志位,而不是寻找新函数。 - 注意 LIMITED API 边界:TSS API 声明被
Py_LIMITED_API >= 0x03070000保护(pythread.h),在 Limited API 模式下确认最低版本设置不低于 3.7 即可直接使用PyThread_tss_*,无需额外条件编译分支。
8. 小结
Doc/deprecations/c-api-pending-removal-in-future.rst 虽然只有一页,但它勾勒出 CPython C API 清理的三条主线:异常处理从三元组模型收敛到单异常对象模型、线程存储从 int 键的 TLS 升级为不透明键的 TSS、以及随 PEP 393 / tp_finalize 内建化而失去存在意义的兼容宏与标志位。对这些条目逐项替换为原档指定的替代接口,并以 Py_DEPRECATED 标记(3.2 / 3.7 / 3.11 各档)作为优先级依据完成迁移,是 C 扩展在后续 CPython 版本中保持长期兼容的最低成本路径。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00