What breaking the stable ABI costs, historical breakages, versioning policy, and the tradeoff against new features.
The CPython Stable ABI is a compatibility layer that allows extension modules compiled against one Python version to continue working across multiple later Python versions without recompilation.
The Stable ABI exists to reduce binary compatibility problems in the Python extension ecosystem.
Without a stable ABI:
extension compiled for Python 3.x
may fail on Python 3.ybecause internal structures, function layouts, macros, or object representations change.
With the Stable ABI:
one compiled extension binary
works across many CPython versionsThis chapter examines:
what ABI stability means
how the Stable ABI differs from the full C API
why ABI stability is difficult
how CPython hides implementation details
tradeoffs between performance and compatibility
limitations of the Stable ABI
interaction with runtime evolution
future compatibility pressuresThe Stable ABI is one of the central engineering compromises in CPython.
96.1 API vs ABI
An API and an ABI are related but different.
API
An API defines source-level interfaces.
Example:
PyObject *PyLong_FromLong(long v);A program including Python headers can compile against this declaration.
ABI
An ABI defines binary-level compatibility.
It includes:
calling conventions
structure layouts
symbol names
memory alignment
register usage
binary object representationTwo versions may share the same API but break ABI compatibility.
Example:
typedef struct {
PyObject_HEAD
long value;
} PyLongObject;If CPython changes this structure layout:
old extension binary may read wrong memory offsetsleading to crashes or corruption.
The Stable ABI attempts to prevent such breakage.
96.2 Why Binary Compatibility Matters
Python has a massive native extension ecosystem.
Examples include:
NumPy
pandas
cryptography
Pillow
lxml
SciPy
database drivers
machine learning librariesCompiling extensions repeatedly for every Python release is expensive.
Without ABI stability:
new Python release
→
all extension wheels rebuiltThis creates:
distribution overhead
CI complexity
installation failures
platform fragmentation
slow ecosystem adoptionA stable ABI reduces this burden.
96.3 CPython’s Historical C API
Historically, CPython exposed many internal implementation details directly.
Extensions could access object internals freely:
PyListObject *list;
list->ob_item[i]or:
Py_TYPE(obj)
obj->ob_refcntThis made extensions fast and flexible.
But it tightly coupled extensions to CPython internals.
If CPython changed:
object layout
allocator behavior
header structure
reference counting semanticsold binaries could break immediately.
The historical API prioritized performance and direct access over abstraction.
96.4 The Stable ABI Goal
The Stable ABI attempts to separate:
public binary interfacefrom:
internal runtime implementationThe key idea:
extensions should use stable exported functions
instead of relying on internal structuresThis allows CPython internals to evolve while preserving binary compatibility.
96.5 The Limited API
The Stable ABI is closely connected to the Limited API.
The Limited API restricts which symbols and structures extensions may use.
Extensions opting into the Limited API define:
#define Py_LIMITED_APIbefore including Python headers.
This changes visible declarations.
Instead of exposing internals directly, the headers expose opaque interfaces.
Example:
PyObject *obj;is still visible.
But internal structure fields may become hidden.
Extensions must then use accessor functions instead of direct structure access.
96.6 Opaque Object Structures
One major design strategy is opaque structures.
Historically:
obj->ob_refcnt
obj->ob_typewere directly accessible.
In a stable ABI model:
internal layout should be hiddenInstead of:
Py_TYPE(obj)extensions may need:
PyObject_Type(obj)or another exported API.
Opaque structures allow CPython to modify internal layouts safely.
96.7 Why Direct Structure Access Is Dangerous
Direct structure access hardcodes layout assumptions into compiled binaries.
Suppose CPython changes:
typedef struct {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;to:
typedef struct {
uintptr_t flags;
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;An old extension expecting the original layout may:
read wrong memory
write invalid offsets
corrupt runtime state
crashOpaque APIs prevent extensions from depending on exact memory layouts.
96.8 Stable ABI Symbol Export
The Stable ABI relies on exported runtime symbols.
Extensions compiled against the Stable ABI call functions dynamically resolved at runtime.
Examples:
PyLong_FromLong
PyList_New
PyObject_GetAttrString
PyImport_ImportModuleThese exported functions remain ABI-stable across versions.
Internals behind them may change freely.
96.9 Why Macros Are Problematic
Traditional CPython uses many macros for speed.
Example:
#define PyList_GET_ITEM(op, i) (((PyListObject *)(op))->ob_item[i])Macros inline structure access directly into extension binaries.
This is fast, but destroys ABI flexibility.
If list layout changes:
compiled macro expansion becomes invalidStable ABI code therefore prefers functions:
PyObject *PyList_GetItem(PyObject *list, Py_ssize_t index);Function calls preserve abstraction boundaries.
96.10 Performance Tradeoffs
This creates one of the core tradeoffs.
Direct Access
Advantages:
faster
inlinable
minimal overhead
full implementation visibilityDisadvantages:
ABI fragile
implementation tightly coupled
runtime evolution constrainedOpaque API
Advantages:
binary compatibility
implementation flexibility
safer runtime evolutionDisadvantages:
extra call overhead
less optimization opportunity
reduced low-level controlThe Stable ABI intentionally sacrifices some performance flexibility for compatibility.
96.11 Runtime Evolution Pressure
Modern CPython increasingly changes runtime internals:
free-threaded execution
immortal objects
specialized interpreters
new allocator strategies
inline caches
layout optimizationsThese changes become difficult if extensions depend on internal layouts.
Stable ABI abstraction helps CPython evolve internally without breaking extensions.
This is increasingly important as interpreter architecture becomes more sophisticated.
96.12 Reference Counting and ABI Stability
Reference counting creates compatibility challenges.
Historically:
Py_INCREF(obj);
Py_DECREF(obj);were often macros manipulating object fields directly.
Future runtimes may change refcount behavior:
atomic refcounts
biased refcounts
immortal objects
thread-local ownershipIf extensions directly manipulate object headers, runtime evolution becomes constrained.
Stable APIs help decouple extension code from internal memory management strategies.
96.13 Stable ABI and Free-Threading
Free-threaded CPython increases pressure for abstraction.
Traditional extensions often assume:
direct refcount access
borrowed reference safety under GIL
unlocked structure access
global runtime assumptionsThese assumptions become unsafe under concurrency.
A more opaque ABI boundary allows CPython to change synchronization mechanisms internally.
Without abstraction, many runtime improvements become nearly impossible.
96.14 Heap Types vs Static Types
Static type definitions historically embedded interpreter assumptions directly into binaries.
Example:
static PyTypeObject MyType = {
PyVarObject_HEAD_INIT(NULL, 0)
};Heap types are more flexible.
They are created dynamically at runtime:
runtime constructs type object
runtime controls layout
runtime may evolve implementationHeap types better support stable interfaces because they reduce static binary coupling.
96.15 The Cost of Abstraction
Every abstraction layer introduces cost.
Example:
PyList_GET_ITEM(list, i)may compile into one memory load.
But:
PyList_GetItem(list, i)requires:
function call
argument passing
possible checks
less compiler optimizationThese differences matter in tight loops.
High-performance extensions sometimes avoid the Stable ABI for this reason.
96.16 Why Some Extensions Avoid the Stable ABI
Many performance-sensitive extensions still target the full CPython API instead of the Stable ABI.
Reasons include:
maximum performance
direct memory access
macro-based fast paths
custom allocator integration
internal structure knowledge
advanced runtime manipulationExamples:
scientific computing
vectorized numeric libraries
custom runtime integrations
advanced debuggers
profilersThe Stable ABI is useful, but not universally optimal.
96.17 Stable ABI Versioning
The Stable ABI evolves conservatively.
CPython guarantees that certain exported interfaces remain available across versions.
Extensions compiled for older Stable ABI targets can often run on newer interpreters unchanged.
Conceptually:
extension compiled against stable ABI version X
→
runs on future CPython versionsThis reduces wheel explosion across Python versions.
96.18 ABI Stability Limits
The Stable ABI cannot guarantee everything.
Certain behaviors still depend on runtime semantics:
threading behavior
GC timing
performance characteristics
object identity details
allocator behavior
internal optimization strategiesThe Stable ABI preserves binary compatibility, not necessarily identical runtime behavior.
96.19 Stable ABI and Inlining
Inlining is difficult across opaque ABI boundaries.
Direct structure access allows compilers to optimize aggressively.
Opaque calls reduce visibility.
Example:
compiler cannot easily inline opaque runtime behaviorThis limits:
constant propagation
dead code elimination
register allocation optimization
specialized memory accessThe Stable ABI therefore constrains certain compiler optimizations.
96.20 Compatibility vs Innovation
This is the central tension.
CPython wants:
runtime innovation
better scalability
new memory models
better concurrency
layout optimizationBut the ecosystem wants:
stable binaries
minimal rebuilds
predictable compatibilityThe Stable ABI balances these competing goals.
Too much exposure freezes internals permanently.
Too much abstraction hurts performance and flexibility for extension authors.
96.21 Borrowed References and ABI Safety
Borrowed references are another pressure point.
Traditional APIs expose patterns like:
PyObject *item = PyList_GET_ITEM(list, i);This depends heavily on runtime invariants.
Future runtimes with:
free-threading
moving GC
different ownership modelsmay require safer APIs.
The Stable ABI increasingly encourages explicit ownership semantics.
96.22 Stable ABI and Alternate Runtimes
A cleaner ABI boundary also helps alternate Python runtimes.
If extensions depend less on CPython internals:
other runtimes can emulate the ABI more easilyThis improves portability across:
| Runtime | Challenge |
|---|---|
| PyPy | Different GC and object model |
| GraalPython | JVM runtime |
| HPy-oriented runtimes | Abstract object handles |
| Experimental CPython runtimes | Internal layout changes |
Opaque interfaces improve runtime flexibility.
96.23 HPy and Future Directions
HPy is a newer extension API effort designed around stronger abstraction.
Instead of exposing raw PyObject * everywhere:
opaque handles
explicit lifetime management
runtime-neutral API boundariesHPy aims to support:
CPython
PyPy
future runtimes
free-threaded interpreters
moving garbage collectorsmore cleanly than the traditional API.
It represents a possible future direction for extension compatibility.
96.24 Why CPython Cannot Fully Hide Internals
Completely hiding internals is difficult.
Some extensions fundamentally require low-level access:
NumPy array internals
custom allocators
JIT integrations
debugging tools
profilers
runtime instrumentationCPython therefore maintains multiple layers:
| Layer | Purpose |
|---|---|
| Full C API | Maximum power and performance |
| Limited API | Safer abstraction |
| Stable ABI | Binary compatibility |
| Internal APIs | CPython implementation use |
This layered approach reflects ecosystem diversity.
96.25 Mental Model
Use this model:
The full CPython API exposes implementation details directly.
This gives:
high performance
deep runtime access
tight coupling to internals
The Stable ABI hides implementation details behind stable exported interfaces.
This gives:
binary compatibility
safer runtime evolution
lower direct optimization freedom
The Stable ABI is therefore an engineering compromise between performance and long-term compatibility.96.26 Chapter Summary
The CPython Stable ABI allows extension modules to remain binary-compatible across multiple Python versions.
Achieving ABI stability requires hiding implementation details behind stable interfaces:
opaque structures
stable exported functions
restricted APIs
reduced direct memory accessThis abstraction enables runtime evolution, including:
free-threading
new refcount strategies
layout optimization
allocator redesignBut it also introduces tradeoffs:
less optimization freedom
higher abstraction overhead
reduced low-level controlThe Stable ABI is fundamentally a balance between compatibility, performance, and runtime evolution flexibility.