Python stable ABI (abi3, abi3t)#
CPython’s stable ABI lets
one extension module run on every CPython release from the version it was
built for. pybind11 supports it from CPython 3.12 on: define
Py_LIMITED_API to 0x030C0000 (or a newer version) and ship one
*.abi3.so / *.pyd per platform instead of one module per Python
version.
CPython 3.15 adds a second stable ABI, abi3t (PEP 803). An abi3t
module loads on every free-threaded and GIL-enabled CPython from 3.15 on, so
one *.abi3t.so per platform covers both builds. pybind11 supports it too:
define Py_TARGET_ABI3T to 0x030F0000 (or a newer version). Headers of
either build work. With free-threaded headers, Py_LIMITED_API alone also
selects abi3t, because free-threaded CPython has no abi3. See abi3t
below for what differs.
Enabling it#
With CMake, add STABLE_ABI to pybind11_add_module (CMake 3.26+ and
the FindPython mode are required), or set PYBIND11_STABLE_ABI to make it
the default for a build tree:
pybind11_add_module(example STABLE_ABI example.cpp)
PYBIND11_STABLE_ABI_VERSION (default 3.12, raised to 3.15 for
abi3t) selects the targeted version. On free-threaded Python the modules
are always abi3t. Set PYBIND11_ABI3T to ON to also build abi3t
modules with GIL-enabled Python (plain CMake only; wheel builders tag the
wheel from the build Python, see Building wheels):
cmake -S . -B build -DPYBIND11_STABLE_ABI=ON -DPYBIND11_ABI3T=ON
If your project calls find_package(Python) itself, add the
Development.SABIModule component; pybind11 adds it only when it finds
Python itself.
PRECOMPILE can be combined with STABLE_ABI; the precompiled library
is then compiled against the limited API too, and one build tree cannot mix
stable-ABI and regular precompiled modules.
With scikit-build-core, set wheel.py-api = "cp312" in pyproject.toml
("cp312.cp315t" to also get abi3t wheels from free-threaded Python;
the pybind11 defaults produce the matching module in both cases). This only
selects the wheel tag, and scikit-build-core ignores it when the build Python
is too old or is not CPython, so enable STABLE_ABI from the variables it
sets, before find_package(pybind11) (scikit-build-core 0.11+ sets
SKBUILD_SABI_VERSION; with older versions it is empty and pybind11 uses its
default):
if(SKBUILD_SABI_COMPONENT)
set(PYBIND11_STABLE_ABI ON)
set(PYBIND11_STABLE_ABI_VERSION "${SKBUILD_SABI_VERSION}" CACHE STRING "")
endif()
If your project calls find_package(Python) itself, add the component that
scikit-build-core selects:
find_package(Python REQUIRED COMPONENTS Interpreter Development.Module
${SKBUILD_SABI_COMPONENT})
With setuptools, pass py_limited_api=True (or a version such as
"3.13") to Pybind11Extension; the module is named *.abi3.so
and Py_LIMITED_API is defined for you. Add a t ("3.15t") for
abi3t, which is always used on free-threaded Python; Py_TARGET_ABI3T
is then defined and the module is named *.abi3t*.so. With GIL-enabled
Python, use build_ext from pybind11.setup_helpers to get that name.
Set the wheel tag with options={"bdist_wheel": {"py_limited_api": "cp312"}}
in setup(); bdist_wheel rejects "cp315t", so pass "cp315" and
build on free-threaded Python to get the abi3.abi3t tag.
With meson-python, set limited-api = true in [tool.meson-python] and
pass limited_api: '3.12' to py.extension_module(). Meson defines
Py_LIMITED_API from that value, so use '3.15' when the build Python is
free-threaded:
limited_api = py.get_variable('Py_GIL_DISABLED', 0).to_string() == '1' ? '3.15' : '3.12'
With any other build system, define Py_LIMITED_API=0x030C0000 for every
translation unit, link no version-specific Python library (python3.lib on
Windows), and name the module <name>.abi3.so (<name>.pyd on Windows).
For abi3t, define Py_TARGET_ABI3T=0x030F0000 instead, link
python3t.lib on Windows, and name the module <name>.abi3t.so.
On CPython 3.15+, build tools can also use the platform-tagged suffixes
.abi3-<platform>.so and .abi3t-<platform>.so (such as
.abi3t-darwin.so). CPython 3.15+ loads both forms, but CPython 3.12 to
3.14 only load .abi3.so.
Building wheels#
Build
abi3wheels with the CPython version you target. On 3.15+, setuptools and CMake name the module.abi3-<platform>.so, so a module that targets 3.12 to 3.14 but is built with 3.15+ has a suffix that its target versions do not load.Build
abi3twheels with free-threaded CPython 3.15+. With GIL-enabled Python, scikit-build-core and setuptools tag the wheelabi3even when the module isabi3t, so the wheel installs where it cannot load.When a
setup.pyruns CMake, pass-DPython_EXECUTABLE=<sys.executable>. FindPython skips free-threaded interpreters by default, so it can find a GIL-enabled Python and build anabi3module into anabi3twheel.Every extension module in an
abi3orabi3twheel must use the stable ABI; one version-specific module makes the wheel tag wrong.
pybind11 rejects the combination with PyPy, GraalPy, and free-threaded CPython before 3.15 at compile time: they have no stable ABI.
What changes under the stable ABI#
The C++ API of pybind11 is the same; the differences are in what is available and in how a few things are implemented.
Feature |
Stable ABI |
Notes |
|---|---|---|
Classes, functions, casters, STL, numpy, Eigen |
supported |
Under abi3t, |
|
supported |
Every instance reserves a |
Buffer protocol |
supported |
Through the |
|
supported |
|
|
restricted |
The metaclass must not define |
|
not available |
Its callback receives a |
|
not available |
Embedding uses non-limited initialization functions. |
|
not available |
Reads thread- and interpreter-state fields. |
|
supported |
The |
GIL-held assertions ( |
not available |
|
abi3t#
Under abi3t, PyObject and PyModuleDef are incomplete types and
PyMutex and PyUnstable_TryIncRef() are not available. pybind11 then:
stores its per-instance data as PEP 697 type data (
PyObject_GetTypeData()) instead of embeddingPyObject_HEAD;exports the module through the PEP 793
PyModExport_<name>hook instead ofPyInit_<name>, soPYBIND11_MODULEis unchanged butpy::module_::create_extension_module(), thepy::module_(name, doc)constructor, and the deprecatedPYBIND11_PLUGINare not available. If you restrict exported symbols (a linker version script,-exported_symbols_list, or a.deffile), exportPyModExport_*too;locks its internals with critical sections on a private object, and keeps one weak reference per registered instance so that the instance registry can hand out strong references safely (
PyWeakref_GetRef()). This weak reference is visible from Python:weakref.getweakrefcount()counts it, andweakref.ref(obj)without a callback returns that same object.
pybind11/numpy.h reads NumPy’s object fields through the accessors NumPy
2.5 added for abi3t, so older NumPy versions are rejected at run time. The
internals tag is _abi3t: an abi3t module and an abi3 module loaded
into the same GIL-enabled interpreter do not share internals (see below).
CPython’s headers define Py_GIL_DISABLED for every abi3t build, so the
macro only says that the code must be thread-safe. To know whether the running
interpreter is free-threaded, check at run time, for example with
sys._is_gil_enabled() or sysconfig.get_config_var("Py_GIL_DISABLED").
ABI isolation#
A stable-ABI module and a regular module can be loaded in one process, but
they do not share pybind11’s internal state: the internals ID carries an
_abi3 or _abi3t tag identifying the compiled Python ABI target
(PEP 803), so each kind of module registers its own types and instances.
Types bound in one kind of module are opaque to the other, except
through the pybind11_conduit_v1 protocol
(include/pybind11/conduit/README.txt), which bridges the two: the C++ ABI
is unchanged, so PYBIND11_PLATFORM_ABI_ID is the same.
Implementation notes and performance#
Without access to CPython’s struct layouts, pybind11 uses public functions where it used to read fields directly. The costs are small and are confined to specific operations:
Type objects are created with
PyType_FromMetaclass()andPyType_Spec. This is also the default for regular builds on CPython 3.12+; definePYBIND11_TYPE_CREATION_VIA_SPEC=0(CMake:-DPYBIND11_TYPE_CREATION_VIA_SPEC=OFF) to use the previous path, which fills in thePyHeapTypeObjectfields directly. DefiningPYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSETin a regular build also selects the previous path, so all bound classes share one dictionary layout.Instance allocation and deallocation look up
tp_alloc/tp_freewithPyType_GetSlot(); the__dict__ofpy::dynamic_attr()instances is a fixed slot in the instance layout.Attribute assignment on bound classes (
Type.static_prop = value) walks__mro__and the class dictionaries instead of using_PyType_Lookup(); attribute reads use the metaclass default and are not affected.Methods are bound through a pybind11-provided
instancemethodtype andtypes.MethodTypeinstead ofPyInstanceMethod_Type/PyMethod_New().Tuple and list element access uses the checked function forms; type names in error messages are reconstructed from
__module__and__name__.py::eval/py::execcompile withPy_CompileString()and run withPyEval_EvalCode();py::eval_filereads the file through Python.
Modules built for the stable ABI check the interpreter version at import: any CPython from the targeted version on is accepted.