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

Python 增强提案

PEP 743 – 在 Python C API 中添加 Py_COMPAT_API_VERSION

作者:
Victor Stinner <vstinner at python.org>,Petr Viktorin <encukou at gmail.com>
状态:
草案
类型:
标准跟踪
创建:
2024-03-11
Python 版本:
3.14

目录

摘要

添加 Py_COMPAT_API_VERSION 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。

将其留给第三方 linter 可能就足够了。为此,我们需要一种好的方法来向这些 linter 公开(软)弃用 API 的列表。在添加该功能的同时,我们可以在 CPython 头文件中相当轻松地直接完成 linter 的工作,从而避免了对额外工具的需求。与 Python 不同,C 使限制可用 API 变得非常容易——对于整个项目或每个单独的源文件——方法是让用户定义一个“选择加入”宏。

我们已经使用 Py_LIMITED_API 做了一些类似的事情,它将可用 API 限制为编译到稳定 ABI 的子集。(事后看来,我们应该为此类限制使用不同的宏名称,但现在更改为时已晚。)

为了防止在识别出更多“不可取”的 API 并为其添加更安全的替代方案时工作代码中断,选择加入宏应为版本化。用户可以根据其兼容性要求选择所需的版本,并根据自己的节奏进行更新。

需要明确的是,此机制不是弃用的替代方案。弃用适用于阻止新功能或优化,或带来安全风险或维护负担的 API。另一方面,此机制适用于“我们找到了一种稍微更好的做事方式”的情况——也许是一种更难以误用或名称不太误导的方式。(轻松一点:许多人配置代码质量检查器以对他们函数之间空行的数量大喊大叫。让我们帮助他们识别更多实质性的“代码异味”!)

建议的宏不会更改任何 API 定义;它只会隐藏它们。因此,如果代码使用宏编译,它也可以不使用宏编译,并且行为相同。这对核心开发人员有影响:为了处理不可取的行为,我们需要引入新的、更好的 API,然后弃用旧的 API。反过来,这意味着我们应该查看单个 API 并一次性修复所有已知问题,而不是对单一类型的问题进行代码库范围的扫描,以避免同一个函数多次重命名。

添加 Py 前缀

选择加入宏允许我们省略可能与第三方库冲突的定义。

规范

我们引入了 Py_COMPAT_API_VERSION 宏。如果在 #include <Python.h> 之前定义了此宏,则一些 API 定义(如下所述)将从 Python 头文件中省略。

该宏仅省略从 <Python.h> 公开的完整顶级定义。其他内容(ABI、结构定义、宏扩展、静态内联函数体等)不受影响。

C API 工作组 (PEP 731) 有权决定省略定义的集合。

省略定义的集合将与 CPython 的特定功能版本相关联,并在每个 3.x.0 Beta 1 版本中最终确定。在极少数情况下,可以随时删除(即使其可供使用)条目。

该宏应定义为 PY_VERSION_HEX 使用的格式的版本,其中“微版本”、“发行版”和“序列号”字段设置为零。例如,要省略在 3.14.0b1 中被认为不可取的 API,用户应将 Py_COMPAT_API_VERSION 定义为 0x030e0000

省略 API 的要求

使用 Py_COMPAT_API_VERSION 省略的 API 必须

  • 被软弃用(参见 PEP 387);
  • 对于 API 的所有已知用例,都具有记录在案的替代方案或解决方法;
  • 有测试确保它继续工作(除了使用 #definetypedef 进行 1:1 重命名);
  • 有文档记录(除非在先前版本的文档中从未提及);以及
  • 获得 C API 工作组的批准。(WG 可以对相关 API 组进行批量批准;有关示例,请参见下面的初始集合。)

请注意,Py_COMPAT_API_VERSION 适用于可以通过更好的替代方案轻松替换的 API。没有替代方案的 API 通常应改为弃用。

位置

所有被 Py_COMPAT_API_VERSION 省略的 API 定义将移动到一个新的头文件 Include/legacy.h 中。

这旨在帮助 linter 作者编译列表,以便他们可以使用警告而不是错误来标记 API。

请注意,对于仅源代码结构(宏、类型)的简单重命名,我们期望在添加替换的相同版本或相同 PR 中省略名称。这意味着原始定义将被重命名,并且 Include/legacy.h 中将添加旧名称的 typedef#define

文档

省略 API 的文档通常应

  • 出现在推荐的替换之后,
  • 引用替换(例如“类似于 X,但…”),以及
  • 重点关注与替换和迁移建议的差异。

如果存在充分的理由,则可以例外。

初始集合

以下 API 将在 Py_COMPAT_API_VERSION 设置为 0x030e0000 (3.14) 或更高版本时被省略

  • 省略返回借用引用的 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_COMPAT_API_VERSION`,则会 `#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` 前缀的类型定义(`getter`,`setter`,`allocfunc`,…),转而使用添加了前缀的新类型定义(`Py_getter` 等)。
  • 软弃用并省略没有 `Py` / `_Py` 前缀的宏(`METH_O`,`CO_COROUTINE`,`FUTURE_ANNOTATIONS`,`WAIT_LOCK`,…),转而使用添加了前缀的新宏(`Py_METH_O` 等)。
  • C API 工作组批准的其他任何宏

如果这些建议的替换或相关文档没有及时添加到 3.14.0b1 中,它们将在 `Py_COMPAT_API_VERSION` 的后续版本中被省略。(我们预计这适用于由 `configure` 生成的宏:`HAVE_*`,`WITH_*`,`ALIGNOF_*`,`SIZEOF_*`,以及一些没有共同前缀的宏。)

实现

待定

未解决的问题

名称 `Py_COMPAT_API_VERSION` 取自之前的 PEP;它不适合此版本。

向后兼容性

该宏向后兼容。开发人员可以根据自己的节奏引入和更新该宏,可能一次更新一个源文件。

讨论

现有技术

  • `Py_LIMITED_API` 宏,来自 PEP 384“定义稳定的 ABI”。
  • 拒绝了 PEP 606“Python 兼容版本”,它具有全局作用域。

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

上次修改:2024-07-25 12:32:18 GMT