PEP 743 – 在 Python C API 中添加 Py_OMIT_LEGACY_API
- 作者:
- Victor Stinner <vstinner at python.org>, Petr Viktorin <encukou at gmail.com>
- PEP 代理人:
- C API 工作组
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 创建日期:
- 2024年3月11日
- Python 版本:
- 3.15
摘要
添加 Py_OMIT_LEGACY_API C 宏,用于隐藏已弃用和软弃用的符号,允许用户选择不使用已知存在问题但有其他 API 解决的 API。
此外,为没有 Py_ 前缀的 API 添加命名空间替代方案,并软弃用原始名称。
动机
Python 的某些 C API 存在缺陷,这些缺陷只有在事后才明显。
如果某个 API 阻止添加功能或优化,或构成严重的安全风险或维护负担,我们可以按照 PEP 387 中的描述弃用并删除它。
然而,这使我们剩下一些“锋利边缘”的 API——它对其当前用户来说运行良好,但在新代码中应避免使用。例如:
- 无法发出异常信号的 API,因此故障要么被忽略,要么以致命错误退出进程。例如
PyObject_HasAttr。 - 非线程安全的 API,例如通过从可变对象借用引用,或暴露未完成的可变对象。例如
PyDict_GetItemWithError。 - 名称不使用
Py/_Py前缀的 API,因此可能与其他代码冲突。例如:setter。
值得注意的是,尽管存在这些缺陷,通常仍可以正确使用 API。例如,在单线程环境中,线程安全不是问题。我们不想破坏工作代码,即使它使用了在某些——甚至大多数——其他上下文中可能是错误的 API。
另一方面,我们希望在新代码中引导用户避免使用这些“不受欢迎”的 API,尤其是在存在更安全的替代方案时。
添加 Py 前缀
CPython 头文件中定义的一些名称没有命名空间:它缺少 Py 前缀(或变体:_Py,以及其他大写形式)。例如,我们声明了一个名为 setter 的函数类型。
虽然这些名称未在 ABI 中导出(通过 make smelly 检查),但它们可能与用户代码冲突,更重要的是,可能与链接到第三方扩展的库冲突。
虽然可以提供命名空间别名并(软)弃用这些名称,但使它们不与第三方代码冲突的唯一方法是根本不在 Python 头文件中定义它们。
基本原理
我们希望允许用户在选择时轻松避免“不受欢迎”的 API。
将其留给第三方代码检查器可能就足够了。为此,我们需要一种好的方式将(软)弃用 API 列表暴露给这些代码检查器。在添加这些功能的同时,我们可以——相当容易地——直接在 CPython 头文件中完成代码检查器的工作,避免需要额外的工具。与 Python 不同,C 通过让用户定义“选择加入”宏,使其非常容易限制可用 API——对于整个项目或每个单独的源文件。
我们已经对 Py_LIMITED_API 做了类似的事情,它将可用 API 限制为编译为稳定 ABI 的子集。(事后看来,我们应该为那种特定的限制使用不同的宏名称,但现在改变已经太晚了。)
需要明确的是,此机制不是弃用的替代品。弃用适用于阻止新功能或优化,或构成安全风险或维护负担的 API。另一方面,此机制适用于“我们发现了一种稍微更好的做事方式”的情况——也许是一种更难被滥用,或者只是有一个更少误导性的名称。(轻松地说:许多人配置代码质量检查器,让他们对函数之间的空行数量大喊大叫。让我们帮助他们识别更实质性的“代码异味”!)
提议的宏不改变任何 API 定义;它只隐藏它们。因此,如果代码使用该宏编译,它在没有该宏的情况下也能编译,并具有相同的行为。这对于核心开发人员有影响:为了处理不良行为,我们需要引入新的、更好的 API,然后劝退旧的 API。反过来,这意味着我们应该查看单个 API 并一次性修复其所有已知问题,而不是对单一类型的问题进行全代码库扫描,从而避免同一函数多次重命名。
添加 Py 前缀
选择加入宏允许我们省略可能与第三方库冲突的定义。
规范
我们引入了一个 Py_OMIT_LEGACY_API 宏。如果在 #include <Python.h> 之前定义此宏,则某些 API 定义——如下所述——将从 Python 头文件中省略。
该宏仅省略从 <Python.h> 公开的完整顶级定义。其他内容(ABI、结构定义、宏展开、静态内联函数体等)不受影响。
C API 工作组 (PEP 731) 对省略定义的集合拥有权限。
省略定义的集合将与 CPython 的特定功能版本挂钩,并在每个 3.x.0 Beta 1 版本中最终确定。在极少数情况下,条目可以随时删除(即恢复可用)。
省略 API 的要求
使用 Py_OMIT_LEGACY_API 省略的 API 必须
- 被软弃用(参见 PEP 387);
- 对于 API 的所有已知用例,都有文档化的替代方案或解决方法;
- 有测试以确保其持续工作(除了使用
#define或typedef进行的 1:1 重命名); - 有文档(除非在以前版本的文档中从未提及);以及
- 经 C API 工作组批准。(工作组可以对相关 API 组进行一揽子批准;参见下面的初始集合以获取示例。)
请注意,Py_OMIT_LEGACY_API 适用于可以被更好替代方案轻松替换的 API。没有替代方案的 API 通常应改为弃用。
位置
所有被 Py_OMIT_LEGACY_API 省略的 API 定义将移至一个新的头文件 Include/legacy.h。
这旨在帮助代码检查器作者编译列表,以便他们可以标记带有警告而不是错误的 API。
请注意,对于仅源构造(宏、类型)的简单重命名,我们期望名称在添加替代项的同一版本——或同一 PR——中省略。这意味着原始定义将被重命名,并且旧名称的 typedef 或 #define 将添加到 Include/legacy.h 中。
文档
省略 API 的文档通常应
- 出现在推荐的替代方案之后,
- 引用替代方案(例如“类似于 X,但……”),以及
- 重点关注与替代方案的区别和迁移建议。
如果它们有充分的理由,则可以例外。
初始集合
以下 API 将在设置 Py_OMIT_LEGACY_API 时省略
- 省略返回借用引用的 API
省略的 API 替代方案 PyDict_GetItem()PyDict_GetItemRef()PyDict_GetItemString()PyDict_GetItemStringRef()PyImport_AddModule()PyImport_AddModuleRef()PyList_GetItem()PyList_GetItemRef() - 省略已弃用的 API
省略的已弃用 API 替代方案 PY_FORMAT_SIZE_T"z"PY_UNICODE_TYPEwchar_tPyCode_GetFirstFree()PyUnstable_Code_GetFirstFree()PyCode_New()PyUnstable_Code_New()PyCode_NewWithPosOnlyArgs()PyUnstable_Code_NewWithPosOnlyArgs()PyImport_ImportModuleNoBlock()PyImport_ImportModule()PyMem_DEL()PyMem_Free()PyMem_Del()PyMem_Free()PyMem_FREE()PyMem_Free()PyMem_MALLOC()PyMem_Malloc()PyMem_NEW()PyMem_New()PyMem_REALLOC()PyMem_Realloc()PyMem_RESIZE()PyMem_Resize()PyModule_GetFilename()PyModule_GetFilenameObject()PyOS_AfterFork()PyOS_AfterFork_Child()PyObject_DEL()PyObject_Free()PyObject_Del()PyObject_Free()PyObject_FREE()PyObject_Free()PyObject_MALLOC()PyObject_Malloc()PyObject_REALLOC()PyObject_Realloc()PySlice_GetIndicesEx()(两次调用;参见当前文档) PyThread_ReInitTLS()(不再需要) PyThread_create_key()PyThread_tss_alloc()PyThread_delete_key()PyThread_tss_free()PyThread_delete_key_value()PyThread_tss_delete()PyThread_get_key_value()PyThread_tss_get()PyThread_set_key_value()PyThread_tss_set()PyUnicode_AsDecodedObject()PyUnicode_Decode()PyUnicode_AsDecodedUnicode()PyUnicode_Decode()PyUnicode_AsEncodedObject()PyUnicode_AsEncodedString()PyUnicode_AsEncodedUnicode()PyUnicode_AsEncodedString()PyUnicode_IS_READY()(不再需要) PyUnicode_READY()(不再需要) PyWeakref_GET_OBJECT()PyWeakref_GetRef()PyWeakref_GetObject()PyWeakref_GetRef()Py_UNICODEwchar_t_PyCode_GetExtra()PyUnstable_Code_GetExtra()_PyCode_SetExtra()PyUnstable_Code_SetExtra()_PyDict_GetItemStringWithError()PyDict_GetItemStringRef()_PyEval_RequestCodeExtraIndex()PyUnstable_Eval_RequestCodeExtraIndex()_PyHASH_BITSPyHASH_BITS_PyHASH_IMAGPyHASH_IMAG_PyHASH_INFPyHASH_INF_PyHASH_MODULUSPyHASH_MODULUS_PyHASH_MULTIPLIERPyHASH_MULTIPLIER_PyObject_EXTRA_INIT(不再需要) _PyThreadState_UncheckedGet()PyThreadState_GetUnchecked()_PyUnicode_AsString()PyUnicode_AsUTF8()_Py_HashPointer()Py_HashPointer()_Py_T_OBJECTPy_T_OBJECT_EX_Py_WRITE_RESTRICTED(不再需要) - 软弃用并省略 API
省略的已弃用 API 替代方案 PyDict_GetItemWithError()PyDict_GetItemRef()PyDict_SetDefault()PyDict_SetDefaultRef()PyMapping_HasKey()PyMapping_HasKeyWithError()PyMapping_HasKeyString()PyMapping_HasKeyStringWithError()PyObject_HasAttr()PyObject_HasAttrWithError()PyObject_HasAttrString()PyObject_HasAttrStringWithError() - 省略
<structmember.h>遗留 API头文件
structmember.h,它不包含在<Python.h>中,必须单独包含,如果定义了Py_OMIT_LEGACY_API,则会产生#error。这会影响以下 API省略的已弃用 API 替代方案 T_SHORTPy_T_SHORTT_INTPy_T_INTT_LONGPy_T_LONGT_FLOATPy_T_FLOATT_DOUBLEPy_T_DOUBLET_STRINGPy_T_STRINGT_OBJECT( tp_getset;待编写文档)T_CHARPy_T_CHART_BYTEPy_T_BYTET_UBYTEPy_T_UBYTET_USHORTPy_T_USHORTT_UINTPy_T_UINTT_ULONGPy_T_ULONGT_STRING_INPLACEPy_T_STRING_INPLACET_BOOLPy_T_BOOLT_OBJECT_EXPy_T_OBJECT_EXT_LONGLONGPy_T_LONGLONGT_ULONGLONGPy_T_ULONGLONGT_PYSSIZETPy_T_PYSSIZETT_NONE( tp_getset;待编写文档)READONLYPy_READONLYPY_AUDIT_READPy_AUDIT_READREAD_RESTRICTEDPy_AUDIT_READPY_WRITE_RESTRICTED(不再需要) RESTRICTEDPy_AUDIT_READ - 省略软弃用的宏
省略的宏 替代方案 Py_IS_NAN()isnan()(C99+<math.h>)Py_IS_INFINITY()isinf(X)(C99+<math.h>)Py_IS_FINITE()isfinite(X)(C99+<math.h>)Py_MEMCPY()memcpy()(C<string.h>) - 软弃用并省略不带
Py/_Py前缀的 typedef(getter、setter、allocfunc等),转而使用添加了前缀的新 typedef(Py_getter等)。 - 软弃用并省略不带
Py/_Py前缀的宏(METH_O、CO_COROUTINE、FUTURE_ANNOTATIONS、WAIT_LOCK等),转而使用添加了前缀的新宏(Py_METH_O等)。 - C API 工作组批准的任何其他内容
如果这些提议的替代方案或相关文档未能及时在 3.14.0b1 中添加,它们将在更高版本的 Py_OMIT_LEGACY_API 中省略。(我们预计这适用于由 configure 生成的宏:HAVE_*、WITH_*、ALIGNOF_*、SIZEOF_* 以及几个没有共同前缀的宏。)
实施
待定
向后兼容性
该宏向后兼容。开发人员可以按照自己的进度引入和更新该宏,可能一次更新一个源文件。
CPython 的未来版本可能会将更多 API 添加到 Py_OMIT_LEGACY_API 隐藏的集合中,从而破坏用户代码。解决方法是取消定义该宏(这是安全的)或重构代码。
讨论
- PEP 743 – 在 Python C API 中添加 Py_COMPAT_API_VERSION(第二次尝试) (2024年7月)
- 完成大重命名 (2024年5月)
- PEP 743: 在 Python C API 中添加 Py_COMPAT_API_VERSION (2024年3月)
- C API 演变:隐藏已弃用函数的宏 (2023年10月)
- C API 问题:用于新干净 API 的选择加入宏?没有已知问题的函数子集 (2023年6月)
先行实践
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0743.rst