I’ve updated PEP 689.
Compared to the previous version, it adds a
PyUnstable_ prefix to all API in the new unstable tier. I still think we could do without it (marking entire files with
Py_USING_UNSTABLE_API), but it’s better than the underscore, which I believe should be reserved for private API.
Since functions will now need to be renamed when they’re moved from one stability tier to another, there’s more focus on not breaking pre-existing names until necessary (i.e. until an incompatible change is made).
What are your thoughts?
There’s one open question: with the
PyUnstable_ prefix, the opt-in macro (
Py_USING_UNSTABLE_API) might not be necessary.
Full text of the PEP
Abstract ======== Some functions and types of the C-API are designated *unstable*, meaning that they will not change in patch (bugfix/security) releases, but may change between minor releases (e.g. between 3.11 and 3.12) without deprecation warnings. Any C API with a leading underscore is designated *internal*, meaning that it may change or disappear without any notice. Motivation & Rationale ====================== Unstable C API tier ------------------- The Python C-API is currently divided into `three stability tiers <https://devguide.python.org/developer-workflow/c-api/index.html>`__: - Limited API, with high compatibility expectations - Public API, which follows the :pep:`backwards compatibility policy <387>`, and requires deprecation warnings before changes - Internal (private) API, which can change at any time. Tools requring access to CPython internals (e.g. advanced debuggers and JIT compilers) are often built for minor series releases of CPython, and assume that the C-API internals used do not change in patch releases. To support these tools, we need a tier between the Public and Private C-API, with guarantees on stability throughout the minor-series release: the proposed *Unstable tier*. Some functions, like ``PyCode_New()``, are documented as unstable (“Calling [it] directly can bind you to a precise Python version”), and also often change in practice. The unstable tier should make their status obvious even to people who don't read the docs carefully enough, making them hard to use accidentally. Reserving leading underscores for Private API --------------------------------------------- Currently, CPython developers don't agree on the exact meaning of a leading underscore in API names. It is used to mean two different things: - API that may change between minor releases, as in the Unstable tier proposed here (e.g. functions introduced in :pep:`523`). - API that is *private* and should not be used outside of CPython at all (e.g. because it may change without notice, or it relies on undocumented assumptions that non-CPython code cannot guarantee). The unclear meaning makes the underscore less useful than it could be. If it only marked *private* API, CPython developers could change underscored functions, or remove unused ones, without researching how they're documented or used outside CPython. With the introduction of an unstable tier, we can clarify the meaning of the leading underscore, eventually making it OK to skip that research. Not breaking code unnecessarily ------------------------------- This PEP specifies that API should be renamed so that the public/unstable/internal stability tier is expressed in function names. Whenever this happens, the old name should continue to be available until an incompatible change is made (i.e. until call sites need to be updated anyway). In other words, just changing tiers shouldn't break users' code. Specification ============= The C API is divided by stability expectations into `three “sections” <https://devguide.python.org/developer-workflow/c-api/index.html>`__ (internal, public, and limited). We'll now call these *stability tiers*, or *tiers* for short. An *Unstable tier* will be added. APIs (functions, types, etc.) in this tier will named with the ``PyUnstable_`` prefix, with no leading underscore. Internally, they will be declared in headers in a new directory, ``Include/unstable/``. Users should include ``Python.h`` rather than using these headers directly. These APIs will only be declared when the ``Py_USING_UNSTABLE_API`` macro is defined. CPython will define the macro for building CPython itself (``Py_BUILD_CORE``). Several rules for dealing with the unstable tier will be introduced: - Unstable API should have no backwards-incompatible changes across patch releases, but may change or be removed in minor releases (3.x.0, including Alpha and Beta releases of 3.x.0). Such changes must be documented and mentioned in the What's New document. - Backwards-incompatible changes to these APIs should be made so that code that uses them will need to be updated to compile with the new version (e.g. arguments should be added/removed, or a function should be renamed, but the semantic meaning of an argument should not change). - To move an API from the public tier to the unstable tier, it should be exposed under the new ``PyUnstable_*`` name and the definition should be guarded with ``Py_USING_UNSTABLE_API``. The old name should be deprecated (e.g. with ``Py_DEPRECATED``), but continue to be available until an incompatible change is made to the API. Per Python's backwards compatibility policy (:pep:`387`), this deprecation needs to last *at least* two releases (without an SC exceptions). But it can also last indefinitely -- for example, if :pep:`590`'s :pep:`“provisional” <590#finalizing-the-api>` ``_PyObject_Vectorcall`` was added today, it would be initially named ``PyUnstable_Object_Vectorcall`` and there would be no plan to ever remove this name. In the following cases, an incompatible change (and thus removing the deprecated name) is allowed without an SC exeption, as if the function was already part of the Unstable tier: - Any API introduced before Python 3.12 that is *documented* to be less stable than default. - Any API introduced before Python 3.12 that was named with a leading underscore. For examples, see the :ref:`initial unstaple API <pep689-initial-list>` specified in this PEP. - To move an *internal* API to the unstable tier, it should be exposed under the new ``PyUnstable_*`` name and the definition should be guarded with ``Py_USING_UNSTABLE_API``. If the old name is documented, or widely used externally, it should continue to be available until an incompatible change is made (and call sites need to be updated). It should start raising deprecation warnings. - To move an API from the unstable tier to the public tier, it should be exposed without the ``PyUnstable_*`` prefix. The old name should remain available, possibly without requiring ``Py_USING_UNSTABLE_API``, until the first incompatible change is made otr the API is removed. - Adding new unstable API *for existing features* is allowed even after the feature freeze, up until the first Release Candidate. Consensus on Core Development Discourse or ``capi-sig`` is needed in the Beta period. These rules will be documented in the `devguide <https://devguide.python.org/developer-workflow/c-api/index.html>`__, and `user documentation <https://docs.python.org/3/c-api/stable.html>`__ will be updated accordingly. Reference docs for C API named ``PyUnstable_*`` will automatically show notes with links to the unstable tier documentation. Leading underscore ------------------ C API named with a leading underscore, as well as API only available with ``Py_BUILD_CORE``, will be considered *internal*. This means: - It may change or be removed *without notice* in minor releases (3.x.0, including Alpha and Beta releases of 3.x.0). API changes in patch releases or Release Candidates should only be done if absolutely necessary. - It should be documented in source comments or Devguide only, not in the public documentation. - API introduced before Python 3.12 that is documented or widely used externally should be moved to the Unstable tier as explained above. This might happen long after this PEP is accepted. Consequently, for a few years core devs should do some research before changing underscored API, especially if it doesn't need ``Py_BUILD_CORE``. Users of the C API are encouraged to search their codebase for ``_Py`` and ``_PY`` identifier prefixes, and treat any hits as issues to be eventually fixed -- either by switching to an existing alternative, or by opening a CPython issue to request exposing public API for their use case, and eventually switching to that. .. _pep689-initial-list: Initial unstable API -------------------- The following API will be moved to the Unstable tier in the initial implementation as proof of the concept. Code object constructors: - ``PyUnstable_Code_New()`` (renamed from ``PyCode_New``) - ``PyUnstable_Code_NewWithPosOnlyArgs()`` (renamed from ``PyCode_NewWithPosOnlyArgs``) Frame evaluation API (:pep:`523`): - ``PyUnstable_FrameEvalFunction`` (renamed from ``_PyFrameEvalFunction``) - ``PyUnstable_InterpreterState_GetEvalFrameFunc()`` (renamed from ``_PyInterpreterState_GetEvalFrameFunc``) - ``PyUnstable_InterpreterState_SetEvalFrameFunc()`` (renamed from ``_PyInterpreterState_SetEvalFrameFunc``) - ``PyUnstable_Eval_RequestCodeExtraIndex()`` (renamed from ``_PyEval_RequestCodeExtraIndex``) - ``PyUnstable_Code_GetExtra()`` (renamed from ``_PyCode_GetExtra``) - ``PyUnstable_Code_SetExtra()`` (renamed from ``_PyCode_SetExtra``) - ``PyUnstable_InterpreterFrame`` (typedef for ``_PyInterpreterFrame``, as an opaque struct) - ``PyUnstable_Frame_GetFrameObject`` (renamed from ``_PyFrame_GetFrameObject``) - ``PyUnstable_EvalFrameDefault`` (new function that calls ``_PyEval_EvalFrameDefault``, but takes ``PyFrameObject`` rather than ``_PyInterpreterFrame``) Backwards Compatibility ======================= The C API backwards compatibility expectations will be made clearer. All renamed API will be available under old names for as long as feasible. How to Teach This ================= The changes affect advanced C programmers, who should consult the updated reference documentation, devguide and/or What's New document. Reference Implementation ======================== https://github.com/python/cpython/issues/91744 Open Issues =========== With the ``PyUnstable_`` prefix, is the opt-in macro necessary? Rejected Ideas ============== No special prefix ----------------- In the initial version of this PEP, unstable API didn't have the ``PyUnstable`` prefix. Instead, defining ``Py_USING_UNSTABLE_API`` made the API available in a given source file, signifying acknowledgement that the file as a whole will potentially need to be revisited for each Python release. However, it was decided that unstable-ness needs to be exposed in the individual names. Underscore prefix ----------------- It would be possible to mark both private and unstable API with leading underscores. However, that would dilute the meaning of ``_Py`` prefix. Reserving the prefix for internal API only makes it trivial to search for. Python API ---------- It might be good to add a similar tier in the Python (not C) API, e.g. for ``types.CodeType``. However, the mechanism for that would need to be different. This is outside the scope of the PEP. Copyright ========= This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.