Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

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 的所有已知用例,都有文档化的替代方案或解决方法;
  • 有测试以确保其持续工作(除了使用 #definetypedef 进行的 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_TYPE wchar_t
    PyCode_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_UNICODE wchar_t
    _PyCode_GetExtra() PyUnstable_Code_GetExtra()
    _PyCode_SetExtra() PyUnstable_Code_SetExtra()
    _PyDict_GetItemStringWithError() PyDict_GetItemStringRef()
    _PyEval_RequestCodeExtraIndex() PyUnstable_Eval_RequestCodeExtraIndex()
    _PyHASH_BITS PyHASH_BITS
    _PyHASH_IMAG PyHASH_IMAG
    _PyHASH_INF PyHASH_INF
    _PyHASH_MODULUS PyHASH_MODULUS
    _PyHASH_MULTIPLIER PyHASH_MULTIPLIER
    _PyObject_EXTRA_INIT (不再需要)
    _PyThreadState_UncheckedGet() PyThreadState_GetUnchecked()
    _PyUnicode_AsString() PyUnicode_AsUTF8()
    _Py_HashPointer() Py_HashPointer()
    _Py_T_OBJECT Py_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_SHORT Py_T_SHORT
    T_INT Py_T_INT
    T_LONG Py_T_LONG
    T_FLOAT Py_T_FLOAT
    T_DOUBLE Py_T_DOUBLE
    T_STRING Py_T_STRING
    T_OBJECT tp_getset;待编写文档)
    T_CHAR Py_T_CHAR
    T_BYTE Py_T_BYTE
    T_UBYTE Py_T_UBYTE
    T_USHORT Py_T_USHORT
    T_UINT Py_T_UINT
    T_ULONG Py_T_ULONG
    T_STRING_INPLACE Py_T_STRING_INPLACE
    T_BOOL Py_T_BOOL
    T_OBJECT_EX Py_T_OBJECT_EX
    T_LONGLONG Py_T_LONGLONG
    T_ULONGLONG Py_T_ULONGLONG
    T_PYSSIZET Py_T_PYSSIZET
    T_NONE tp_getset;待编写文档)
    READONLY Py_READONLY
    PY_AUDIT_READ Py_AUDIT_READ
    READ_RESTRICTED Py_AUDIT_READ
    PY_WRITE_RESTRICTED (不再需要)
    RESTRICTED Py_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(gettersetterallocfunc 等),转而使用添加了前缀的 typedef(Py_getter 等)。
  • 软弃用并省略不带 Py/_Py 前缀的宏(METH_OCO_COROUTINEFUTURE_ANNOTATIONSWAIT_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 384“定义稳定 ABI”的 Py_LIMITED_API 宏。
  • 被拒绝的 PEP 606“Python 兼容性版本”具有全局范围。

来源:https://github.com/python/peps/blob/main/peps/pep-0743.rst

上次修改:2025-09-30 12:20:21 GMT