首页
/ CPython C API 移除预告:Doc/deprecations 中“Pending Removal”弃用 API 全解与替代接口迁移指南

CPython C API 移除预告:Doc/deprecations 中“Pending Removal”弃用 API 全解与替代接口迁移指南

2026-09-06 13:42:18作者:曹令琨Iris

本文以 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_keyPyThread_tss_alloc(配合 PyThread_tss_create 使用);PyThread_delete_keyPyThread_tss_freePyThread_set_key_valuePyThread_tss_setPyThread_get_key_valuePyThread_tss_getPyThread_delete_key_valuePyThread_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_GetFilenamePyModule_GetFilenameObject

旧接口返回 const char *,声明处标注 Py_DEPRECATED(3.2)moduleobject.h);新接口 PyModule_GetFilenameObjectmoduleobject.h)返回 PyObject * 字符串对象。核心区别在编码处理:const char * 受文件系统编码约束且生命周期绑定内部缓存,字符串对象则由调用方按引用计数管理,语义更清晰。迁移时注意新接口返回值需要释放引用。

6.2 PyOS_AfterForkPyOS_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_AfterForkPy_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 扩展中排查这些废弃调用

  1. 打开弃用诊断Py_DEPRECATED 宏最终映射到编译器的 deprecated 属性。用 -Wall -Wdeprecated-declarations(MSVC 对应 /W4 下的 C4996)重新编译扩展,本清单中的接口会逐一报警告。
  2. 按头文件快速核对:上表所列声明均可在当前仓库直接查证——异常处理见 Include/pyerrors.h,线程存储见 Include/pythread.h,切片见 Include/sliceobject.h,模块见 Include/moduleobject.h,fork 见 Include/intrcheck.h,字符串与类型标志见 Include/cpython/unicodeobject.hInclude/object.h,bytes 字段见 Include/cpython/bytesobject.h
  3. 区分“换接口”与“直接删除”两类迁移PyErr_Fetch/Restore、TLS 系列、PySlice_GetIndicesExPyModule_GetFilenamePyOS_AfterFork 属于有替代接口的替换;而 PyUnicode_READYPyThread_ReInitTLSPy_TPFLAGS_HAVE_FINALIZE 属于能力内建化后的冗余项,正确迁移是删除调用/标志位,而不是寻找新函数。
  4. 注意 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 版本中保持长期兼容的最低成本路径。

登录后查看全文
热门项目推荐
相关项目推荐