Skip to content

67. Stable ABI

The stable ABI symbol set, abi3 wheel tagging, and the constraints imposed by maintaining ABI compatibility.

The stable ABI is CPython’s binary compatibility interface for extension modules. It lets an extension module be compiled once and loaded by multiple CPython versions, as long as those versions support the requested ABI level.

The normal CPython C API exposes many implementation details. An extension compiled against those details often needs a separate binary wheel for each Python version. The stable ABI reduces that burden by restricting the extension to a smaller, more stable set of functions, types, and macros.

The stable ABI is closely related to the limited API:

TermMeaning
Limited APISource-level subset selected at compile time
Stable ABIBinary-level interface promised across CPython versions
Py_LIMITED_APICompile-time macro that asks headers to expose the limited API
abi3Wheel tag used for stable-ABI extension binaries

67.1 Why the Stable ABI Exists

Python extension modules are native binaries. A binary compiled for one CPython version may depend on details that differ in another version.

Examples of unstable details:

object struct layout
type object internals
frame internals
thread state internals
private macros
bytecode details
interpreter state structures

Without a stable ABI, package authors often publish wheels like:

cp310-cp310-manylinux_x86_64.whl
cp311-cp311-manylinux_x86_64.whl
cp312-cp312-manylinux_x86_64.whl
cp313-cp313-manylinux_x86_64.whl

With the stable ABI, one binary can target multiple CPython versions:

cp38-abi3-manylinux_x86_64.whl

This reduces build matrix size and simplifies distribution.

67.2 Normal ABI vs Stable ABI

The normal CPython ABI gives extensions broad access to CPython implementation details.

Stable ABI restricts that access.

AreaNormal ABIStable ABI
Object layoutOften visibleMostly opaque
Struct fieldsOften directly accessedUsually hidden
MacrosMay inspect internalsRestricted or function-backed
CompatibilityVersion-specificCross-version
PerformanceMaximum possibleSlightly more indirect
PowerFull CPython-specific accessSmaller supported surface

The normal ABI is useful for extensions that need maximum speed or deep CPython integration.

The stable ABI is useful for extensions that value binary portability.

67.3 The Limited API

The limited API is the source-level mechanism for building against the stable ABI.

An extension opts in with:

#define Py_LIMITED_API 0x03080000
#include <Python.h>

This means:

use the limited API available since Python 3.8

The macro value is a hexadecimal version number:

Macro valueTarget minimum
0x03080000Python 3.8
0x03090000Python 3.9
0x030A0000Python 3.10
0x030B0000Python 3.11
0x030C0000Python 3.12

If you target an older limited API version, your binary can run on more Python versions, but you can use fewer APIs.

67.4 The abi3 Wheel Tag

Python wheels use compatibility tags.

A normal CPython extension wheel may use a tag like:

cp312-cp312-manylinux_x86_64

This means it targets CPython 3.12 specifically.

A stable ABI wheel may use:

cp38-abi3-manylinux_x86_64

This means:

built for CPython
uses stable ABI
requires at least Python 3.8
platform is manylinux x86_64

The abi3 tag is the packaging signal that the extension avoids version-specific CPython ABI dependencies.

67.5 What Becomes Opaque

The stable ABI makes many structures opaque.

Under the full API, code may access fields directly:

Py_TYPE(obj)
Py_REFCNT(obj)
type->tp_name
type->tp_basicsize

Under the limited API, direct field access is reduced. Extension code should prefer accessor functions and supported APIs.

For example, instead of depending on layout details, use operations such as:

PyObject_Type(obj)
PyObject_GetAttrString(obj, "name")
PyLong_AsLong(obj)
PyUnicode_AsUTF8String(obj)

The stable ABI works by preserving callable entry points rather than preserving every internal structure layout.

67.6 Why Struct Layout Is a Problem

C struct layout is part of the binary interface.

Suppose an extension compiles against:

typedef struct {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
    int field_a;
} SomeObject;

If a later CPython version changes the layout:

typedef struct {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
    void *new_field;
    int field_a;
} SomeObject;

old native code may read the wrong memory offset.

This is why stable ABI code avoids direct layout assumptions. Opaque pointers allow CPython to change internals while keeping function signatures stable.

67.7 Example Stable ABI Extension

A small extension can target the limited API:

#define Py_LIMITED_API 0x03080000
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
demo_add(PyObject *self, PyObject *args)
{
    long a;
    long b;

    if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
        return NULL;
    }

    return PyLong_FromLong(a + b);
}

static PyMethodDef methods[] = {
    {"add", demo_add, METH_VARARGS, "Add two integers"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    "demo",
    "Stable ABI demo module",
    -1,
    methods
};

PyMODINIT_FUNC
PyInit_demo(void)
{
    return PyModule_Create(&module);
}

This style avoids direct object layout access and uses stable C API functions.

67.8 Building with Setuptools

A minimal setup file:

from setuptools import Extension, setup

setup(
    name="demo",
    ext_modules=[
        Extension(
            "demo",
            ["demo.c"],
            py_limited_api=True,
            define_macros=[
                ("Py_LIMITED_API", "0x03080000"),
            ],
        )
    ],
)

For wheels, build configuration must also emit the abi3 tag. Many projects use bdist_wheel options or modern build backend settings for this.

The important rule is that both the compiled extension and the wheel metadata must agree that the extension uses the stable ABI.

67.9 What the Stable ABI Allows

Stable ABI extensions can still do many useful things:

create modules
define functions
create Python objects
parse arguments
raise exceptions
call Python callables
manipulate lists and dicts through API functions
work with Unicode
work with bytes
use capsules
use buffers through supported APIs
define heap types through supported mechanisms

For many extension modules, this is enough.

A wrapper around a C library often fits well:

Python arguments
convert to C values
call native library
convert result to Python object

This does not usually require direct access to CPython internals.

67.10 What the Stable ABI Restricts

Stable ABI code should avoid:

direct struct field access
private `_Py*` APIs
internal headers
version-specific frame internals
direct bytecode assumptions
direct type object mutation
macros that expand to internal layout access
interpreter private state

Some performance-oriented extensions depend on these details and cannot use the stable ABI easily.

For example, an extension that heavily inspects frame objects, modifies type internals, or uses private vectorcall details may need the full CPython API.

67.11 Static Types vs Heap Types

Stable ABI design prefers heap types over static PyTypeObject definitions.

A static type often requires direct initialization of a PyTypeObject struct:

static PyTypeObject MyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "demo.MyType",
    .tp_basicsize = sizeof(MyObject),
    .tp_new = My_new,
};

This exposes dependence on the layout of PyTypeObject.

Heap types use PyType_Spec and slot definitions:

static PyType_Slot My_slots[] = {
    {Py_tp_new, My_new},
    {Py_tp_dealloc, My_dealloc},
    {0, NULL}
};

static PyType_Spec My_spec = {
    .name = "demo.MyType",
    .basicsize = sizeof(MyObject),
    .itemsize = 0,
    .flags = Py_TPFLAGS_DEFAULT,
    .slots = My_slots,
};

Then create the type at runtime:

PyObject *type = PyType_FromSpec(&My_spec);

This avoids exposing the full PyTypeObject layout to the extension binary.

67.12 Stable ABI Type Design

A stable ABI extension type should use:

PyType_Spec
PyType_Slot
heap allocation
module state
accessor functions
documented slot IDs

Prefer this shape:

module init
    create heap type
    add type to module
    keep state in module
    avoid static mutable Python objects

This aligns better with modern CPython work on subinterpreters and runtime isolation.

67.13 Reference Counting Still Applies

The stable ABI does not remove manual ownership.

This code still returns a new reference:

return PyLong_FromLong(42);

This code still requires cleanup:

PyObject *s = PyUnicode_FromString("hello");
if (s == NULL) {
    return NULL;
}

Py_DECREF(s);

The stable ABI changes what binary symbols and structures the extension can use. It does not change C API ownership rules.

67.14 Performance Tradeoffs

Stable ABI may cost performance in some cases.

Reasons:

less direct field access
more function calls
fewer private fast paths
less access to version-specific optimizations

For many extensions, this overhead is small because most time is spent in the native library or algorithm.

For extensions that wrap high-frequency Python object operations, the cost can be more visible.

Good candidates:

compression libraries
database bindings
cryptography wrappers
file format parsers
image codecs
native algorithms with coarse calls

Poorer candidates:

extensions doing many tiny object-level operations
profilers using private frame internals
debuggers relying on interpreter structures
JIT or bytecode tools
performance-critical custom containers

67.15 ABI Compatibility vs API Compatibility

API compatibility means source code still compiles.

ABI compatibility means an existing binary still loads and works.

These are different.

CompatibilityQuestion
APICan I recompile this source?
ABICan I reuse this compiled binary?

The stable ABI is about ABI compatibility.

An extension using the full C API may remain source-compatible across Python versions, yet still require recompilation for each version.

67.16 The Cost of Depending on Internals

Private CPython internals can be tempting.

Example motivations:

avoid allocation
read type fields directly
access frame state
skip error checks
use private fast paths
reuse internal helper functions

But each private dependency increases maintenance cost.

Risks:

compile failure on new Python versions
binary crash after upgrade
subinterpreter incompatibility
debug-build differences
free-threading incompatibility
platform-specific bugs

Stable ABI code gives up these shortcuts in exchange for stronger binary durability.

67.17 Stable ABI and Free-Threaded CPython

Free-threaded CPython work makes ABI discipline more important.

Code that assumes details such as direct reference count layout, global interpreter state, or GIL-protected global mutation may become fragile.

The stable ABI encourages extensions to use documented operations rather than internal fields. This does not automatically make an extension free-threading-safe, but it reduces dependence on implementation details that are likely to evolve.

Thread safety still requires separate design:

protect native state
avoid unsafe globals
respect Python object ownership
use documented thread APIs
avoid hidden interpreter assumptions

67.18 Stable ABI and Subinterpreters

Subinterpreters also favor stable, isolated extension design.

Good stable ABI design avoids:

static mutable Python objects
cached interpreter-specific objects
global borrowed references
direct interpreter state pointers
single global module state

Prefer:

per-module state
heap types
capsules with immutable function tables
explicit initialization
documented APIs

The stable ABI and subinterpreter-safe design are not identical, but they point in the same direction: fewer hidden process-global assumptions.

67.19 When to Use the Stable ABI

Use the stable ABI when:

you want fewer wheels
your extension wraps a native library
your API surface is modest
you can avoid private CPython internals
binary portability matters more than maximum micro-optimization

Use the full API when:

you need CPython internals
you depend on private performance paths
you implement low-level runtime tooling
you inspect frames deeply
you need APIs outside the limited subset

A library may also split itself:

stable ABI core wrapper
    +
version-specific optional accelerator

This gives broad compatibility with optional specialized speedups.

67.20 Practical Checklist

Before choosing stable ABI, check:

QuestionGood answer
Can the extension avoid private _Py* APIs?Yes
Can it avoid direct struct field access?Yes
Can it use heap types instead of static types?Yes
Is most work done in native code rather than Python object churn?Yes
Does it need frame or bytecode internals?No
Does it require one wheel across Python versions?Yes

If most answers align, stable ABI is a strong choice.

67.21 Common Mistakes

MistakeResult
Defining Py_LIMITED_API but using private APIsBuild or runtime failure
Building with limited API but publishing wrong wheel tagInstaller confusion
Directly initializing static PyTypeObjectABI coupling
Assuming stable ABI gives source portability to all versionsIt only covers selected APIs
Ignoring reference ownershipLeaks or crashes
Caching interpreter-specific globalsSubinterpreter bugs
Using macros that access struct fieldsBreaks limited API discipline

Stable ABI requires consistent build, code, and packaging choices.

67.22 Chapter Summary

The stable ABI is CPython’s cross-version binary interface for extension modules. It lets one compiled extension binary work across multiple CPython versions by restricting code to the limited API.

The limited API is selected with Py_LIMITED_API. The resulting wheel usually uses the abi3 tag. This reduces wheel build matrices and improves binary portability.

The tradeoff is reduced access to CPython internals. Stable ABI extensions should avoid direct struct access, private APIs, static type layout assumptions, and interpreter-specific global state.

For many native wrappers, codecs, parsers, database drivers, and compute libraries, the stable ABI is a practical default. For extensions that need deep runtime internals or maximum object-level speed, the full CPython API remains necessary.