PEP 743 – Add Py_COMPAT_API_VERSION to the Python C API (take 2)

Since the previous version of this PEP, I’ve teamed up with Victor to rewrite it. The result is available as the current version of PEP 743, and quoted below.

In short, we propose a single macro that will:

  • Hide “non-recommended API” – API that’s soft-deprecated, and easy to replace with a variant. In effect, we’re playing the role of a rudimentary linter (but the aim is also to provide better, dogfooded data for actual linters, which can do advanced things like ignore individual warnings).
  • Hide deprecated API.
  • Hide API that lacks the Py prefix, so it can clash with other libraries. Note that this is something a linter can’t help with.

The macro is versioned, so users can upgrade (or not) at their own pace. Each version is frozen at 3.x.0b1.

The macro name is kept from the previous version of the PEP; it’s up for bikeshedding.

What do y’all think?

Click for full text (with broken ReST formatting)

Abstract

Add Py_COMPAT_API_VERSION C macro that hides some deprecated and
soft-deprecated symbols, allowing users to opt out of using API with known
issues that other API solves.
The macro is versioned, allowing users to update (or not) on their own pace.

Also, add namespaced alternatives for API without the Py_ prefix,
and soft-deprecate the original names.

Motivation

Some of Python’s C API has flaws that are only obvious in hindsight.

If an API prevents adding features or optimizations, or presents a serious
security risk or maintenance burden, we can deprecate and remove it as
described in :pep:387.

However, this leaves us with some API that has “sharp edges” – it works fine
for its current users, but should be avoided in new code.
For example:

  • API that cannot signal an exception, so failures are either ignored or
    exit the process with a fatal error. For example PyObject_HasAttr.
  • API that is not thread-safe, for example by borrowing references from
    mutable objects, or exposing unfinished mutable objects. For example
    PyDict_GetItemWithError.
  • API with names that don’t use the Py/_Py prefix, and so can clash
    with other code. For example: setter.

It is important to note that despite such flaws, it’s usually possible
to use the API correctly. For example, in a single-threaded environment,
thread safety is not an issue.
We do not want to break working code, even if it uses API that would be wrong
in some – or even most – other contexts.

On the other hand, we want to steer users away from such “undesirable” API
in new code, especially if a safer alternative exists.

Adding the Py prefix

Some names defined in CPython headers is not namespaced: it that lacks the
Py prefix (or a variant: _Py, and alternative capitalizations).
For example, we declare a function type named simply setter.

While such names are not exported in the ABI (as checked by make smelly),
they can clash with user code and, more importantly, with libraries linked
to third-party extensions.

While it would be possible to provide namespaced aliases and (soft-)deprecate
these names, the only way to make them not clash with third-party code is to
not define them in Python headers at all.

Rationale

We want to allow an easy way for users to avoid “undesirable” API if they
choose to do so.

It might be be sufficient to leave this to third-party linters.
For that we’d need a good way to expose a list of (soft-)deprecated
API to such linters.
While adding that, we can – rather easily – do the linter’s job directly
in CPython headers, avoiding the neel for an extra tool.
Unlike Python, C makes it rather easy to limit available API – for a whole
project or for each individual source file – by having users define
an “opt-in” macro.

We already do something similar with Py_LIMITED_API, which limits the
available API to a subset that compiles to stable ABI. (In hindsight, we should
have used a different macro name for that particular kind of limiting, but it’s
too late to change that now.)

To prevent working code from breaking as we identify more “undesirable” API
and add safer alternatives to it, the opt-in macro should be versioned.
Users can choose a version they need based on their compatibility requirements,
and update it at their own pace.

To be clear, this mechanism is not a replacement for deprecation.
Deprecation is for API that prevents new features or optimizations, or
presents a security risk or maintenance burden.
This mechanism, on the other hand, is meant for cases where “we found
a slightly better way of doing things” – perhaps one that’s harder to misuse,
or just has a less misleading name.
(On a lighter note: many people configure a code quality checker to shout at
them about the number of blank lines between functions. Let’s help them
identify more substantial “code smells”!)

The proposed macro does not change any API definitions; it only hides them.
So, if code compiles with the macro, it’ll also compile without it, with
identical behaviour.
This has implications for core devs: to deal with undesirable behaviour,
we’ll need to introduce new, better API, and then discourage the old one.
In turn, this implies that we should look at an individual API and fix all its
known issues at once, rather than do codebase-wide sweeps for a single kind of
issue, so that we avoid multiple renames of the same function.

Adding the Py prefix

An opt-in macro allows us to omit definitions that could clash with
third-party libraries.

Specification

We introduce a Py_COMPAT_API_VERSION macro.
If this macro is defined before #include <Python.h>, some API definitions
– as described below – will be omitted from the Python header files.

The macro only omits complete top-level definitions exposed from <Python.h>.
Other things (the ABI, structure definitions, macro expansions, static inline
function bodies, etc.) are not affected.

The C API working group (:pep:731) has authority over the set of omitted
definitions.

The set of omitted definitions will be tied to a particular feature release
of CPython, and is finalized in each 3.x.0 Beta 1 release.
In rare cases, entries can be removed (i.e. made available for use) at any
time.

The macro should be defined to a version in the format used by
PY_VERSION_HEX, with the “micro”, “release” and “serial” fields
set to zero.
For example, to omit API deemed undesirable in 3.14.0b1, users should define
Py_COMPAT_API_VERSION to 0x030e0000.

Requirements for omitted API

An API that is omitted with Py_COMPAT_API_VERSION must:

  • be soft-deprecated (see :pep:387);
  • for all known use cases of the API, have a documented alternative
    or workaround;
  • have tests to ensure it keeps working (except for 1:1 renames using
    #define or typedef);
  • be documented (except if it was never mentioned in previous versions of the
    documentation); and
  • be approved by the C API working group. (The WG may give blanket approvals
    for groups of related API; see Initial set below for examples.)

Note that Py_COMPAT_API_VERSION is meant for API that can be trivially
replaced by a better alternative.
API without a replacement should generally be deprecated instead.

Location

All API definitions omitted by Py_COMPAT_API_VERSION will be moved to
a new header, Include/legacy.h.

This is meant to help linter authors compile lists, so they can flag the API
with warnings rather than errors.

Note that for simple renaming of source-only constructs (macros, types), we
expect names to be omitted in the same version – or the same PR – that adds
a replacement.
This means that the original definition will be renamed, and a typedef
or #define for the old name added to Include/legacy.h.

Documentation

Documentation for omitted API should generally:

  • appear after the recommended replacement,
  • reference the replacement (e.g. “Similar to X, but…”), and
  • focus on differences from the replacement and migration advice.

Exceptions are possible if there is a good reason for them.

Initial set

The following API will be omitted with Py_COMPAT_API_VERSION set to
0x030e0000 (3.14) or greater:

  • Omit API returning borrowed references:

    ==================================== ==============================
    Omitted API Replacement
    ==================================== ==============================
    PyDict_GetItem() PyDict_GetItemRef()
    PyDict_GetItemString() PyDict_GetItemStringRef()
    PyImport_AddModule() PyImport_AddModuleRef()
    PyList_GetItem() PyList_GetItemRef()
    ==================================== ==============================

  • Omit deprecated APIs:

    ==================================== ==============================
    Omitted Deprecated API Replacement
    ==================================== ==============================
    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() (two calls; see current docs)
    PyThread_ReInitTLS() (no longer needed)
    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() (no longer needed)
    PyUnicode_READY() (no longer needed)
    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 (no longer needed)
    _PyThreadState_UncheckedGet() PyThreadState_GetUnchecked()
    _PyUnicode_AsString() PyUnicode_AsUTF8()
    _Py_HashPointer() Py_HashPointer()
    _Py_T_OBJECT Py_T_OBJECT_EX
    _Py_WRITE_RESTRICTED (no longer needed)
    ==================================== ==============================

  • Soft-deprecate and omit APIs:

    ==================================== ==============================
    Omitted Deprecated API Replacement
    ==================================== ==============================
    PyDict_GetItemWithError() PyDict_GetItemRef()
    PyDict_SetDefault() PyDict_SetDefaultRef()
    PyMapping_HasKey() PyMapping_HasKeyWithError()
    PyMapping_HasKeyString() PyMapping_HasKeyStringWithError()
    PyObject_HasAttr() PyObject_HasAttrWithError()
    PyObject_HasAttrString() PyObject_HasAttrStringWithError()
    ==================================== ==============================

  • Omit <structmember.h> legacy API:

    The header file structmember.h, which is not included from <Python.h>
    and must be included separately, will #error if
    Py_COMPAT_API_VERSION is defined.
    This affects the following API:

    ==================================== ==============================
    Omitted Deprecated API Replacement
    ==================================== ==============================
    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; docs to be written)
    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; docs to be written)
    READONLY Py_READONLY
    PY_AUDIT_READ Py_AUDIT_READ
    READ_RESTRICTED Py_AUDIT_READ
    PY_WRITE_RESTRICTED (no longer needed)
    RESTRICTED Py_AUDIT_READ
    ==================================== ==============================

  • Omit soft deprecated macros:

    ====================== =====================================
    Omitted Macros Replacement
    ====================== =====================================
    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>)
    ====================== =====================================

  • Soft-deprecate and omit typedefs without the Py/_Py prefix
    (getter, setter, allocfunc, …), in favour of new ones
    that add the prefix (Py_getter , etc.)

  • Soft-deprecate and omit macros without the Py/_Py prefix
    (METH_O, CO_COROUTINE, FUTURE_ANNOTATIONS, WAIT_LOCK, …),
    favour of new ones that add the prefix (Py_METH_O , etc.).

  • Any others approved by the C API workgroup

If any of these proposed replacements, or associated documentation,
are not added in time for 3.14.0b1, they’ll be omitted with later versions
of Py_COMPAT_API_VERSION.
(We expect this for macros generated by configure: HAVE_*, WITH_*,
ALIGNOF_*, SIZEOF_*, and several without a common prefix.)

Implementation

TBD

Open issues

The name Py_COMPAT_API_VERSION was taken from the earlier PEP;
it doesn’t fit this version.

Backwards Compatibility

The macro is backwards compatible.
Developers can introduce and update the macro on their own pace, potentially
for one source file at a time.

Discussions

  • C API Evolutions: Macro to hide deprecated functions <https://github.com/capi-workgroup/api-evolution/issues/24>_
    (October 2023)
  • C API Problems: Opt-in macro for a new clean API? Subset of functions with no known issues <https://github.com/capi-workgroup/problems/issues/54>_
    (June 2023)
  • Finishing the Great Renaming <https://discuss.python.org/t/finishing-the-great-renaming/54082>_
    (May 2024)

Prior Art

  • Py_LIMITED_API macro of :pep:384 “Defining a Stable ABI”.
  • Rejected :pep:606 “Python Compatibility Version” which has a global
    scope.

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.

3 Likes

My suggestion for the opt-in macro bikeshed colour: Py_RECOMMENDED_API

The PEP itself looks good to me. PEP 667 lists a few borrowed-reference APIs that have recommended replacements as of Python 3.13, so could also go in the initial set of no longer recommended APIs (only PyEval_GetLocals is going to be hard deprecated, the rest are only soft-deprecated)

2 Likes

The PEP ran into opposition on the C API WG issue.
In particular, Steve instead wants a #define Py_WARN_ON_SOFT_DEPRECATION 1 that would warn, rather than hide API.
Let’s move the discussion back here.

@steve.dower, two questions for you:

  • How would you handle macros?
  • What would be your solution to unprefixed names, like getter, that can clash with other libraries?

Another thing that came up is that API deprecated this way could be moved to a separate header, to be more easily usable as data for linters, to consolidate the #if blocks, and to remove cruft from the other headers.
(For the record, I admit that our documentation does not provide a list of soft-deprecated API.)

Presumably you mean how to warn people that they’re deprecated? Which is exactly the same question as how to do it under any other deprecation? So my answer is, “the same as every other time we deprecate a macro”.

If you actually want a warning, then I’d say they get deprecated and turned into an inline function, which can be annotated with a deprecation marker. With very few exceptions, we ought to be able to rely on optimisers to inline them, and if users have performance concerns then they can stop using deprecated macros and switch to the non-deprecated one whenever they like.

Uh… same thing? If we can annotate it, then we annotate it. If we can’t, then we just publish a list of names that are going away and people will eventually discover it when they update.


FTR, I’m +1 on “don’t do this at all”, +0 on “warn rather than hide”, and -1 on “add more orthogonal macros to our headers” (the original proposal). So that’s why I’m quite happy to just exclude complicated bits - because I’m quite happy to just exclude everything from this proposal!

The PEP has been updated:

  • The macro Py_COMPAT_API_VERSION was renamed to Py_OMIT_LEGACY_API.
  • The macro has no value anymore. Previously, it required a Python version.

The macro usage should now be simpler and its intent (macro name) as well.

1 Like

I’ve submitted the PEP to the SC: PEP 743 – Add Py_OMIT_LEGACY_API to the Python C API · Issue #315 · python/steering-council · GitHub

3 Likes

Here is an attempt for build a struct of it:

Here is a thorny issues: some header files require the macros in “pyconfig.h” which are non-standard so that these macros in “pyconfig.h” cannot “do not be defined at first”, so I use a tool, when user define the macro “Py_OMIT_LEGACY_API”, the “legacy.h” will undefine them at last.

We can use the code below to find them:

import os
import subprocess
import re

repo_root = subprocess.check_output(
    ["git", "rev-parse", "--show-toplevel"],
    text=True
).strip()
os.chdir(repo_root)
print(f"Switched to repo root: {repo_root}")

grep_cmd = [
    "git", "grep", "-nE", r"^[[:space:]]*#ifn?def[[:space:]]+[A-Za-z_][A-Za-z0-9_]*",
    "--", "Include/"
]
result = subprocess.run(grep_cmd, capture_output=True, text=True)

lines = result.stdout.splitlines()
pattern = re.compile(r"#ifn?def\s+([A-Za-z_][A-Za-z0-9_]*)")

filtered = []
for line in lines:
    match = pattern.search(line)
    if not match:
        continue
    macro = match.group(1)
    if macro.startswith(("Py", "PY", "_Py", "_PY")):
        continue
    if macro == "__cplusplus":
        continue
    filtered.append(line)

output_path = os.path.join(repo_root, "non_py_macros.txt")
with open(output_path, "w", encoding="utf-8") as f:
    f.write("\n".join(filtered))

print(f"Done. Found {len(filtered)} non-Py macros.")
print(f"Result saved to: {output_path}")

Finally you can get (some of the macros which start with “HAVE_”, “WITH_” are defined in “pyconfig.h”):

Include/Python.h:26:#ifdef HAVE_SYS_TYPES_H
Include/Python.h:63:#ifdef _MSC_VER
Include/Python.h:151:#ifdef _MSC_VER
Include/cpython/initconfig.h:91:#ifdef MS_WINDOWS
Include/cpython/initconfig.h:175:#ifdef MS_WINDOWS
Include/cpython/initconfig.h:184:#ifdef __APPLE__
Include/cpython/object.h:397:#ifdef NDEBUG
Include/cpython/pthread_stubs.h:8:#ifndef _POSIX_THREADS
Include/cpython/pthread_stubs.h:21:#ifdef __wasi__
Include/cpython/pthread_stubs.h:95:#ifndef PTHREAD_KEYS_MAX
Include/cpython/pydebug.h:24:#ifdef MS_WINDOWS
Include/cpython/pyerrors.h:64:#ifdef MS_WINDOWS
Include/cpython/pyerrors.h:88:#ifdef MS_WINDOWS
Include/cpython/pymem.h:22:#ifdef WITH_PYMALLOC
Include/cpython/pymem.h:26:#ifdef WITH_MIMALLOC
Include/cpython/pythread.h:15:#ifdef HAVE_PTHREAD_H
Include/datetime.h:4:#ifndef DATETIME_H
Include/dynamic_annotations.h:56:#ifndef __DYNAMIC_ANNOTATIONS_H__
Include/dynamic_annotations.h:59:#ifndef DYNAMIC_ANNOTATIONS_ENABLED
Include/fileutils.h:8:#ifdef HAVE_SYS_STAT_H
Include/fileutils.h:14:#ifndef S_IFMT
Include/fileutils.h:18:#ifndef S_IFLNK
Include/fileutils.h:23:#ifndef S_ISREG
Include/fileutils.h:26:#ifndef S_ISDIR
Include/fileutils.h:29:#ifndef S_ISCHR
Include/fileutils.h:32:#ifndef S_ISLNK
Include/internal/mimalloc/mimalloc.h:8:#ifndef MIMALLOC_H
Include/internal/mimalloc/mimalloc/atomic.h:8:#ifndef MIMALLOC_ATOMIC_H
Include/internal/mimalloc/mimalloc/atomic.h:139:#ifdef _WIN64
Include/internal/mimalloc/mimalloc/atomic.h:239:#ifdef _WIN64
Include/internal/mimalloc/mimalloc/atomic.h:363:#ifdef __APPLE__
Include/internal/mimalloc/mimalloc/internal.h:8:#ifndef MIMALLOC_INTERNAL_H
Include/internal/mimalloc/mimalloc/internal.h:214:#ifndef __has_builtin
Include/internal/mimalloc/mimalloc/internal.h:226:#ifndef EAGAIN         // double free
Include/internal/mimalloc/mimalloc/internal.h:229:#ifndef ENOMEM         // out of memory
Include/internal/mimalloc/mimalloc/internal.h:232:#ifndef EFAULT         // corrupted free-list or meta-data
Include/internal/mimalloc/mimalloc/internal.h:235:#ifndef EINVAL         // trying to free an invalid pointer
Include/internal/mimalloc/mimalloc/internal.h:238:#ifndef EOVERFLOW      // count*size overflow
Include/internal/mimalloc/mimalloc/internal.h:636:  #ifdef MI_ENCODE_FREELIST
Include/internal/mimalloc/mimalloc/internal.h:648:  #ifdef MI_ENCODE_FREELIST
Include/internal/mimalloc/mimalloc/internal.h:658:  #ifdef MI_ENCODE_FREELIST
Include/internal/mimalloc/mimalloc/internal.h:674:  #ifdef MI_ENCODE_FREELIST
Include/internal/mimalloc/mimalloc/prim.h:8:#ifndef MIMALLOC_PRIM_H
Include/internal/mimalloc/mimalloc/prim.h:134:#ifdef MI_PRIM_THREAD_ID
Include/internal/mimalloc/mimalloc/prim.h:282:    #ifdef __GNUC__
Include/internal/mimalloc/mimalloc/track.h:8:#ifndef MIMALLOC_TRACK_H
Include/internal/mimalloc/mimalloc/track.h:108:#ifndef mi_track_resize
Include/internal/mimalloc/mimalloc/track.h:112:#ifndef mi_track_align
Include/internal/mimalloc/mimalloc/track.h:116:#ifndef mi_track_init
Include/internal/mimalloc/mimalloc/track.h:120:#ifndef mi_track_mem_defined
Include/internal/mimalloc/mimalloc/track.h:124:#ifndef mi_track_mem_undefined
Include/internal/mimalloc/mimalloc/track.h:128:#ifndef mi_track_mem_noaccess
Include/internal/mimalloc/mimalloc/types.h:8:#ifndef MIMALLOC_TYPES_H
Include/internal/mimalloc/mimalloc/types.h:26:#ifdef _MSC_VER
Include/internal/mimalloc/mimalloc/types.h:32:#ifndef MI_MAX_ALIGN_SIZE
Include/internal/mimalloc/mimalloc/types.h:634:#ifndef MI_STAT
Include/internal/pycore_bitutils.h:28:#ifdef _MSC_VER
Include/internal/pycore_ceval.h:67:#ifdef HAVE_FORK
Include/internal/pycore_condvar.h:11:#ifdef _POSIX_THREADS
Include/internal/pycore_condvar.h:17:#ifdef HAVE_PTHREAD_H
Include/internal/pycore_condvar.h:34:#ifndef WIN32_LEAN_AND_MEAN
Include/internal/pycore_faulthandler.h:11:#ifdef HAVE_SIGACTION
Include/internal/pycore_faulthandler.h:16:#ifndef MS_WINDOWS
Include/internal/pycore_faulthandler.h:24:#ifdef HAVE_SIGACTION
Include/internal/pycore_faulthandler.h:36:#ifdef FAULTHANDLER_USER
Include/internal/pycore_faulthandler.h:56:#ifdef MS_WINDOWS
Include/internal/pycore_faulthandler.h:79:#ifdef FAULTHANDLER_USER
Include/internal/pycore_faulthandler.h:83:#ifdef FAULTHANDLER_USE_ALT_STACK
Include/internal/pycore_fileutils.h:16:#ifdef _MSC_VER
Include/internal/pycore_fileutils.h:63:#ifdef MS_WINDOWS
Include/internal/pycore_fileutils.h:135:#ifdef HAVE_READLINK
Include/internal/pycore_fileutils.h:144:#ifdef HAVE_REALPATH
Include/internal/pycore_fileutils.h:176:#ifdef MS_WINDOWS
Include/internal/pycore_fileutils.h:240:#ifdef HAVE_NON_UNICODE_WCHAR_T_REPRESENTATION
Include/internal/pycore_fileutils.h:254:#ifdef MS_WINDOWS
Include/internal/pycore_fileutils.h:310:#ifndef MS_WINDOWS
Include/internal/pycore_fileutils_windows.h:11:#ifdef MS_WINDOWS
Include/internal/pycore_gc.h:230:#ifndef NDEBUG
Include/internal/pycore_gc.h:275:#ifndef NDEBUG
Include/internal/pycore_gil.h:55:#ifdef FORCE_SWITCHING
Include/internal/pycore_import.h:35:#ifdef HAVE_DLOPEN
Include/internal/pycore_importdl.h:17:#ifdef HAVE_DYNAMIC_LOADING
Include/internal/pycore_importdl.h:29:#ifdef MS_WINDOWS
Include/internal/pycore_importdl.h:67:#ifndef MS_WINDOWS
Include/internal/pycore_importdl.h:92:#ifdef HAVE_DYNAMIC_LOADING
Include/internal/pycore_importdl.h:128:#ifdef HAVE_DYNAMIC_LOADING
Include/internal/pycore_importdl.h:142:#ifdef MS_WINDOWS
Include/internal/pycore_initconfig.h:16:#ifdef _MSC_VER
Include/internal/pycore_initconfig.h:55:#ifndef NDEBUG
Include/internal/pycore_interp_structs.h:312:#ifdef HAVE_DLOPEN
Include/internal/pycore_interp_structs.h:411:#ifndef PRIVATE_MEM
Include/internal/pycore_interp_structs.h:874:#ifdef HAVE_FORK
Include/internal/pycore_interpframe.h:230:#ifndef NDEBUG
Include/internal/pycore_mimalloc.h:22:#ifdef WITH_MIMALLOC
Include/internal/pycore_object.h:528:#ifdef NDEBUG
Include/internal/pycore_obmalloc.h:175:#ifdef WITH_MEMORY_LIMITS
Include/internal/pycore_obmalloc.h:176:#ifndef SMALL_MEMORY_LIMIT
Include/internal/pycore_obmalloc.h:210:#ifdef USE_LARGE_ARENAS
Include/internal/pycore_obmalloc.h:218:#ifdef WITH_MEMORY_LIMITS
Include/internal/pycore_obmalloc.h:225:#ifdef USE_LARGE_POOLS
Include/internal/pycore_obmalloc.h:579:#ifdef USE_INTERIOR_NODES
Include/internal/pycore_obmalloc.h:632:#ifdef USE_INTERIOR_NODES
Include/internal/pycore_obmalloc.h:647:#ifdef USE_INTERIOR_NODES
Include/internal/pycore_obmalloc.h:693:#ifdef WITH_PYMALLOC
Include/internal/pycore_opcode_metadata.h:36:#ifdef NEED_OPCODE_METADATA
Include/internal/pycore_opcode_metadata.h:519:#ifdef NEED_OPCODE_METADATA
Include/internal/pycore_opcode_metadata.h:1069:#ifdef NEED_OPCODE_METADATA
Include/internal/pycore_opcode_metadata.h:1317:#ifdef NEED_OPCODE_METADATA
Include/internal/pycore_opcode_metadata.h:1506:#ifdef NEED_OPCODE_METADATA
Include/internal/pycore_opcode_metadata.h:1748:#ifdef NEED_OPCODE_METADATA
Include/internal/pycore_opcode_metadata.h:1773:#ifdef NEED_OPCODE_METADATA
Include/internal/pycore_opcode_metadata.h:2073:#ifdef NEED_OPCODE_METADATA
Include/internal/pycore_pyhash.h:74:#ifndef MS_WINDOWS
Include/internal/pycore_pylifecycle.h:21:#ifdef MS_WINDOWS
Include/internal/pycore_pymath.h:86:#ifdef HAVE_GCC_ASM_FOR_X87
Include/internal/pycore_pymath.h:143:#ifdef HAVE_GCC_ASM_FOR_MC68881
Include/internal/pycore_pystate.h:97:#ifndef NDEBUG
Include/internal/pycore_pystate.h:251:#ifdef HAVE_FORK
Include/internal/pycore_pystate.h:296:#ifndef NDEBUG
Include/internal/pycore_pythread.h:62:#ifdef _USE_PTHREADS
Include/internal/pycore_pythread.h:90:#ifdef HAVE_FORK
Include/internal/pycore_runtime.h:24:#ifdef HAVE_FORK
Include/internal/pycore_runtime_structs.h:50:#ifndef MS_WINDOWS
Include/internal/pycore_semaphore.h:12:#ifdef MS_WINDOWS
Include/internal/pycore_signal.h:20:#ifdef _SIG_MAXSIG
Include/internal/pycore_signal.h:47:#ifdef MS_WINDOWS
Include/internal/pycore_signal.h:58:#ifdef MS_WINDOWS
Include/internal/pycore_signal.h:71:#ifdef MS_WINDOWS
Include/internal/pycore_signal.h:82:#ifdef MS_WINDOWS
Include/internal/pycore_signal.h:99:#ifdef MS_WINDOWS
Include/internal/pycore_stackref.h:680:#ifdef _WIN32
Include/internal/pycore_stackref.h:774:#ifdef _WIN32
Include/internal/pycore_stackref.h:808:#ifdef _WIN32
Include/internal/pycore_stackref.h:840:#ifdef _WIN32
Include/internal/pycore_time.h:62:#ifdef __clang__
Include/internal/pycore_time.h:174:#ifdef MS_WINDOWS
Include/internal/pycore_time.h:180:#ifndef MS_WINDOWS
Include/internal/pycore_tracemalloc.h:40:#ifdef __GNUC__
Include/internal/pycore_tracemalloc.h:49:#ifdef _MSC_VER
Include/internal/pycore_uop_metadata.h:21:#ifdef NEED_OPCODE_METADATA
Include/intrcheck.h:9:#ifdef HAVE_FORK
Include/methodobject.h:119:#ifdef STACKLESS
Include/object.h:584:#ifdef STACKLESS
Include/osdefs.h:16:#ifdef MS_WINDOWS
Include/osdefs.h:23:#ifdef __VXWORKS__
Include/osdefs.h:28:#ifndef SEP
Include/osdefs.h:33:#ifdef __hpux
Include/osdefs.h:41:#ifndef MAXPATHLEN
Include/osdefs.h:50:#ifndef DELIM
Include/py_curses.h:5:#ifdef __APPLE__
Include/py_curses.h:10:#ifdef _BSD_WCHAR_T_DEFINED_
Include/py_curses.h:53:#ifdef NCURSES_VERSION
Include/py_curses.h:65:#ifndef MVWDELCH_IS_EXPRESSION
Include/py_curses.h:88:#ifdef CURSES_MODULE
Include/pydtrace.h:9:#ifdef WITH_DTRACE
Include/pyerrors.h:145:#ifdef MS_WINDOWS
Include/pyerrors.h:192:#ifdef MS_WINDOWS
Include/pylifecycle.h:40:#ifdef MS_WINDOWS
Include/pymacconfig.h:8:#ifdef __APPLE__
Include/pymacconfig.h:33:#ifndef __LP64__
Include/pymacconfig.h:38:#ifdef __LP64__
Include/pymacconfig.h:73:#ifdef __BIG_ENDIAN__
Include/pymacro.h:197:#ifdef WITH_DOC_STRINGS
Include/pyport.h:4:#ifndef UCHAR_MAX
Include/pyport.h:16:#ifdef __has_builtin
Include/pyport.h:24:#ifdef __has_attribute
Include/pyport.h:114:#ifndef HAVE_LONG_LONG
Include/pyport.h:153:#ifdef HAVE_PY_SSIZE_T
Include/pyport.h:242:#ifdef SIGNED_RIGHT_SHIFT_ZERO_FILLS
Include/pyport.h:401:#ifndef INT_MAX
Include/pyport.h:405:#ifndef LONG_MAX
Include/pyport.h:415:#ifndef LONG_MIN
Include/pyport.h:419:#ifndef LONG_BIT
Include/pyport.h:458:#ifdef __SUNPRO_C
Include/pyport.h:478:#ifdef WORDS_BIGENDIAN
Include/pyport.h:486:#ifdef __ANDROID__
Include/pyport.h:499:#ifndef WITH_THREAD
Include/pyport.h:511:#ifdef WITH_THREAD
Include/pystrcmp.h:11:#ifdef MS_WINDOWS
Include/pythonrun.h:27:#ifdef USE_STACKCHECK
Include/unicodeobject.h:63:#ifndef SIZEOF_WCHAR_T
Include/unicodeobject.h:86:#ifdef HAVE_USABLE_WCHAR_T
Include/unicodeobject.h:253:#ifdef HAVE_WCHAR_H
Include/unicodeobject.h:613:#ifdef MS_WINDOWS

So I apply for that the macros in “pyconfig.h” will have a different way to be handled.

I think you may be missing the main point of this PEP.

It’s really about providing C extension authors with an way to test that they’re still using the best practices and that their extension will keep working into the future (without having to wait for the future).

It isn’t really about “undefing everything in pyconfig.h”.

The cleanup doesn’t have to be comprehensive to be useful (if you believe the PEP will be useful…), it doesn’t have to be done in one go, and the most value is probably in covering deprecated Python functions (i.e. the stuff packages are actually using) instead of all the odd names brought in by the C config.

2 Likes

So, the warning file is also easy to generate.
Here is the tool script to handle the “pyconfig.h”. Take me some day to finish it, and it can finish it in an easier way.

But I have no right to decide it. The issue to add the document for C-functions is active now, and before it, there is no idea of which function should be add to “legacy.h”.

There’s already a long list of deprecated c API (e.g. What’s new in Python 3.14 — Python 3.14.0 documentation ) so there’s no need to decide anything new

The deprecated APIs are already known — agreed.
But implementing all of them inside the PEP 743 PR mixes structural changes with behavioral ones.

Even with no new decisions to make, that would create an oversized, high-risk PR.

The PEP 743 infrastructure should land first.
Deprecated API removals should follow in separate, focused PRs.

Now it only handles “pyconfig.h” because it can use this struct to simplify implement, the other deperecating need extra PRs.