# PEP 803: Stable ABI for Free-Threaded Builds

**URL:** https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628
**Category:** PEPs
**Created:** [September 8, 2025, 1:32pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628 "2025-09-08T13:32:22Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![encukou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/encukou/32/2461_2.png) [@encukou](https://discuss.python.org/u/encukou)
#### Post date: [September 8, 2025, 1:32pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/1 "2025-09-08T13:32:22Z")

</div>

Hello,  
I’ve just published [PEP 803 ­– Stable ABI for Free-Threaded Builds](https://peps.python.org/pep-0803/), based on [latest post in the previous thread](https://discuss.python.org/t/86458/49). Abstract:

> Version 3.15 of the Stable ABI will be compatible with both free-threaded and GIL-enabled builds. To allow this, the [`PyObject`](https://docs.python.org/3/c-api/structures.html#c.PyObject) internal structure and related APIs will be removed from version 3.15 of the Limited API, requiring migration to new API for common tasks like defining modules and most classes.

Now you can argue against a specific plan, rather than several possibilities!  
Most alterantives from the previous thread are still possible though, so if you see something blocking you – especially in the PEP’s Rationale – do speak up.

My goal is that 3.15.0a1 should include a “MVP" of the CPython parts – enough that I can ask packaging tools to enable this, so we can start exposing it to users and getting feedback from real code.  
That’s in a month, so if you want to speak up, please do so sooner. (Especially if you’re not going to the core dev sprint next week, before which there’s a [SC deadline](https://discuss.python.org/t/pep-793-pymodexport-a-new-entry-point-for-c-extension-modules/93444/19) that might enable some development.)

Note that the PEP combines several pieces, some of which make sense individually. In particular, I’ve already merged in the [`Py_mod_abi`](https://docs.python.org/3.15/c-api/module.html#c.Py_mod_abi) slot.  
(Of course, I’m ready to remove anything that turns out to have been a bad idea.)

---

<div class="post-metadata">

### Author: ![ngoldbaum](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ngoldbaum/32/12276_2.png) [@ngoldbaum](https://discuss.python.org/u/ngoldbaum)
#### Post date: [September 8, 2025, 2:07pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/2 "2025-09-08T14:07:51Z")

</div>

This is really nice! I’m glad to see that you’re making a lot of progress on this.

Looking toward a future where NumPy decides to start shipping stable ABI wheels, I noticed just now there’s one spot NumPy uses `Py_SIZE` to get the size of a type to use for estimating memory usage:

> <https://github.com/numpy/numpy/blob/0c83f4f70d639b7b9c447230e1249d0b0c8c4157/numpy/_core/src/multiarray/scalartypes.c.src#L1720-L1732>

Once `PyObject` is opaque, is there still a way to get at the total memory used by an object to do things like this?

To support the other changes in this PEP, NumPy would need to do a large-scale refactoring to avoid static types, so this is more of a theoretical concern, but maybe other extensions use `Py_SIZE` like this to get memory usage diagnostics.

---

<div class="post-metadata">

### Author: ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)
#### Post date: [September 8, 2025, 2:55pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/3 "2025-09-08T14:55:50Z")

</div>

I like the overall plan and the specification.

I would just suggest converting `Py_SIZE()` and `Py_SET_SIZE()` static inline functions to regular (opaque) functions, rather than removing them.

---

<div class="post-metadata">

### Author: ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)
#### Post date: [September 8, 2025, 5:08pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/4 "2025-09-08T17:08:33Z")

</div>

So essentially, we’re proposing to simply make ABI3 compiled pre-3.15 to refuse to load on 3.15 and later? So if your extension uses the Stable ABI, you’ll need two builds that are both tagged `abi3` but the old one is `py3-abi3` and the new one is `cp315.cp316.cp317...-abi3` (until you stop supporting 3.14 and earlier and can just make the new one `py3`?\[1\]

* * *

1. I’m 99% sure the compressed tag will be selected over `py3`, but admit I could be wrong about that. I think things are worse if I’m wrong, though…

---

<div class="post-metadata">

### Author: ![effigies](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/effigies/32/35835_2.png) [@effigies](https://discuss.python.org/u/effigies)
#### Post date: [September 8, 2025, 5:15pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/5 "2025-09-08T17:15:26Z")

</div>

If cp314-abi3 and cp315-abi3 are mutually unloadable, why not just call it abi4?

---

<div class="post-metadata">

### Author: ![thomas](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/thomas/32/5492_2.png) [@thomas](https://discuss.python.org/u/thomas)
#### Post date: [September 8, 2025, 6:14pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/6 "2025-09-08T18:14:56Z")

</div>

> [@steve.dower](#):
>
> So essentially, we’re proposing to simply make ABI3 compiled pre-3.15 to refuse to load on 3.15 and later?

I don’t think that’s correct? It’ll refuse to load _in free-threaded builds_ of 3.15, but builds with the GIL should still work.

---

<div class="post-metadata">

### Author: ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)
#### Post date: [September 8, 2025, 6:26pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/7 "2025-09-08T18:26:12Z")

</div>

Okay then, replace 3.15 with 3.16 or whenever freethreaded becomes the default.

---

<div class="post-metadata">

### Author: ![thomas](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/thomas/32/5492_2.png) [@thomas](https://discuss.python.org/u/thomas)
#### Post date: [September 8, 2025, 6:47pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/8 "2025-09-08T18:47:59Z")

</div>

Probably not 3.16, but even then, it may be the _default_ but it will not be _the only build_. We’re talking about years and years of the _ability_ for people to choose to use packages that require the older ABI by choosing _not_ to use free-threaded Python. That’s why it’s not just abi4.

---

<div class="post-metadata">

### Author: ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)
#### Post date: [September 8, 2025, 7:03pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/9 "2025-09-08T19:03:53Z")

</div>

Do we have a more concrete plan than [https://peps.python.org/pep-0703/#python-build-modes](https://peps.python.org/pep-0703/#python-build-modes) then? (The “possible path” shown there is already clearly not going to happen, since “optional with env var + compatible ABI” is now known to not be possible, but I haven’t seen an officially approved alternative.)

---

<div class="post-metadata">

### Author: ![jamestwebber](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamestwebber/32/12799_2.png) [@jamestwebber](https://discuss.python.org/u/jamestwebber)
#### Post date: [September 8, 2025, 7:34pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/10 "2025-09-08T19:34:59Z")

</div>

I think he’s referring to this: [PEP 703 (Making the Global Interpreter Lock Optional in CPython) acceptance](https://discuss.python.org/t/pep-703-making-the-global-interpreter-lock-optional-in-cpython-acceptance/37075)

In particular, the end of Phase III mentions the possibility of maybe phasing out the GIL eventually, but there’s no timeline.

---

<div class="post-metadata">

### Author: ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)
#### Post date: [September 8, 2025, 8:20pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/11 "2025-09-08T20:20:29Z")

</div>

Sure, but this question and PEP goes directly to the open issue in [PEP 779 – Criteria for supported status for free-threaded Python | peps.python.org](https://peps.python.org/pep-0779/), which was [directed by the SC](https://discuss.python.org/t/pep-779-criteria-for-supported-status-for-free-threaded-python/84319/123) to be discussed for 3.15, but no specific design was mandated. That’s what we’re doing here (and Petr and I have largely been doing spread over a number of locations since before PEP 779 was accepted).

My primary concern with any of the proposals so far\[1\] is dividing the community for an unspecified length of time.\[2\] The choices largely come down to one of:

- `abi3` is broken at an arbitrarily time
- `abi3t` exists until free-threading becomes default and then `abi3` no longer exists
- `abi3` and `abi4` exist in parallel, the latter supported by both builds, and `abi3` no longer exists once free-threading becomes the default
- free-threading has no stable ABI until it becomes the default (status quo, but already rejected by the SC)

I don’t particularly have specific desires about the _contents_ of the new ABI\[3\] - virtually everything I’ve seen proposed has been pretty uncontroversial IMHO - it’s just that some of it is _necessarily_ breaking the ABI and I think option 3 above manages that better than options 1 or 2.

* * *

1. I haven’t had time to work up more than a sketch of a proposal, but my core criteria follows. 

2. I originally wrote “permanently” knowing that’s not true, but it definitely flows better to use a touch of hyperbole and say that my primary concern is permanently dividing the community. 

3. I have some ideas about future longevity, so we aren’t changing it every release like we have been with `abi3`, but those are totally separate from the free-threading issue, e.g. they could also be added to `abi3` safely.

---

<div class="post-metadata">

### Author: ![encukou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/encukou/32/2461_2.png) [@encukou](https://discuss.python.org/u/encukou)
#### Post date: [September 9, 2025, 7:18am UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/12 "2025-09-09T07:18:26Z")

</div>

> [@vstinner](#):
>
> I would just suggest converting `Py_SIZE()` and `Py_SET_SIZE()` static inline functions to regular (opaque) functions, rather than removing them.

Yeah, let’s do that now.  
(I hoped to get rid of `Py_SIZE` eventually: `PyType_GenericAlloc()` and friends don’t need to store the size since the _caller_ has it and can stash it in a field of their choosing, and that field is then more obviously private to the type – or made public, if the author wishes. But, removing friction in porting existing extensions is now more important than phasing out confusing designs.)

> [@steve.dower](#):
>
> So if your extension uses the Stable ABI, you’ll need two builds that are both tagged `abi3` but the old one is `py3-abi3` and the new one is `cp315.cp316.cp317...-abi3` (until you stop supporting 3.14 and earlier and can just make the new one `py3`?

No, `cp315-abi3` will load on CPython 3.16+. The Python tag has always been a lower bound.

> [@steve.dower](#):
>
> (The “possible path” shown [there](https://peps.python.org/pep-0703/#python-build-modes) is already clearly not going to happen, since “optional with env var + compatible ABI” is now known to not be possible, but I haven’t seen an officially approved alternative.)

Yes. That “possible path” is for non-stable ABI; a universal ABI is indeed not possible there at this point.

For PEP 803, an important premise is that building an extension twice (or rather, a “small” number of times), for different ABIs, is not an issue for extension authors.  
If that premise is wrong, let’s do things differently.

> [@steve.dower](#):
>
> 1. `abi3` is broken at an arbitrarily time
> 2. `abi3t` exists until free-threading becomes default and then `abi3` no longer exists
> 3. `abi3` and `abi4` exist in parallel, the latter supported by both builds, and `abi3` no longer exists once free-threading becomes the default
> 4. free-threading has no stable ABI until it becomes the default (status quo, but already rejected by the SC)
> 
> I don’t particularly have specific desires about the _contents_ of the new ABI - virtually everything I’ve seen proposed has been pretty uncontroversial IMHO - it’s just that some of it is _necessarily_ breaking the ABI and I think option 3 above manages that better than options 1 or 2.

After the Python 2→3 transition, we decided (for Python API), that there would not be a version 4, and instead any necessary breaking changes would be introduced gradually, with deprecation periods. Old code will stop working after enough warning is given.  
[That](https://peps.python.org/pep-0387/) is also the policy for _behaviour_ of the C API. Functions “behind“ the ABI can already start raising warnings and then stop working. A “stricter“ ABI means that you get “nice“ exceptions rather than memory corruption or “symbol not found“ errors that prevent loading the _whole_ extension.

This PEP essentially does that for Stable ABI. There will be no `abi4`, and instead **old versions** of `abi3` will be broken after some years of notice.

---

<div class="post-metadata">

### Author: ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)
#### Post date: [September 9, 2025, 8:53am UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/13 "2025-09-09T08:53:21Z")

</div>

> [@encukou](#):
>
> The Python tag has always been a lower bound.

Technically, the Python tag (like any other tag) is only accepted by environments that state they accept it. By convention, Python 3.14 states that it accepts `cp313` wheels (this is implemented in the `packaging` library), but it’s not a requirement.

Steve is technically correct, although in practice `cp315-abi3` should be fine.

> [@encukou](#):
>
> For PEP 803, an important premise is that building an extension twice (or rather, a “small” number of times), for different ABIs, is not an issue for extension authors.

I’d strongly advise bringing that up in the packaging category. My impression is that there are many projects where the number of versions they have to build is already problematic, and doubling that would definitely be an issue.

> [@encukou](#):
>
> This PEP essentially does that for Stable ABI. There will be no `abi4`, and instead **old versions** of `abi3` will be broken after some years of notice.

That seems to contradict the term “stable”, or at least the expectations people have of that term, though…?

---

<div class="post-metadata">

### Author: ![philthompson](https://avatars.discourse-cdn.com/v4/letter/p/ed8c4c/32.png) [@philthompson](https://discuss.python.org/u/philthompson)
#### Post date: [September 9, 2025, 9:35am UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/14 "2025-09-09T09:35:24Z")

</div>

> > [@encukou](#):
> >
> > The Python tag has always been a lower bound.
> 
> Technically, the Python tag (like any other tag) is only accepted by  
> environments that state they accept it. By convention, Python 3.14  
> states that it accepts `cp313` wheels (this is implemented in the  
> `packaging` library), but it’s not a requirement.
> 
> Steve is technically correct, although in practice `cp315-abi3` should  
> be fine.

The bit I’m struggling with is understanding whether or not existing  
`cp39-abi3` (for example) wheels will be supported by Python v3.15???

> > [@encukou](#):
> >
> > For PEP 803, an important premise is that building an extension twice  
> > (or rather, a “small” number of times), for different ABIs, is not an  
> > issue for extension authors.
> 
> I’d strongly advise bringing that up in the packaging category. My  
> impression is that there are many projects where the number of  
> versions they have to build is already problematic, and doubling that  
> would definitely be an issue.

Indeed. My own burden is 15 (related) projects on 5 platforms (counting  
macOS Intel and ARM as one). The stable ABI makes this manageable. I  
have no plans to support free-threaded builds until a stable ABI is  
available. Things would be easier (but not impossible) if that ABI  
supported the existing ABI for a couple of years or so.

---

<div class="post-metadata">

### Author: ![pitrou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pitrou/32/28_2.png) [@pitrou](https://discuss.python.org/u/pitrou)
#### Post date: [September 9, 2025, 10:22am UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/15 "2025-09-09T10:22:46Z")

</div>

> [@philthompson](#):
>
> The bit I’m struggling with is understanding whether or not existing  
> `cp39-abi3` (for example) wheels will be supported by Python v3.15???

The PEP should definitely be explicit on this IMHO.

---

<div class="post-metadata">

### Author: ![encukou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/encukou/32/2461_2.png) [@encukou](https://discuss.python.org/u/encukou)
#### Post date: [September 9, 2025, 10:33am UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/16 "2025-09-09T10:33:23Z")

</div>

> [@pf\_moore](#):
>
> That seems to contradict the term “stable”, or at least the expectations people have of that term, though…?

Yes. But for free-threading, the stable ABI needs to be broken in _some_ way.

> [@philthompson](#):
>
> The bit I’m struggling with is understanding whether or not existing  
> `cp39-abi3` (for example) wheels will be supported by Python v3.15???

Yes, but only in the regular (non-free-threaded) builds of 3.15+.

> [@philthompson](#):
>
> Things would be easier (but not impossible) if that ABI  
> supported the existing ABI for a couple of years or so.

It _should_ be possible to make a single ABI compatible with 3.11+ (non-free-threaded) and 3.15+ (all builds).\[1\] Personally, I’d be happy to do it – please convince the SC that it’s a good idea! Currently it seems the complexity in testing and supporting this is not worth it.  
It could be done as an “add-on” to PEP 803 – basically, extending the compatibility to lower versions.

* * *

1. ① Add `Py_OPAQUE_OBJECT` as a user-settable knob for limited API 3.14 and below; ② add a shim for PEP 793’s `PyModExport` (ugly but self-contained); ③ add a few shims for things that only became non-static functions in or 3.15; ④ add a `abi3t` wheel tag, rather than relying on `cp315-abi3`

---

<div class="post-metadata">

### Author: ![Monarch](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/monarch/32/18530_2.png) [@Monarch](https://discuss.python.org/u/Monarch)
#### Post date: [September 9, 2025, 10:42am UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/17 "2025-09-09T10:42:24Z")

</div>

I’m not very familiar with the stable ABI, but I do publish `abi3` wheels (mostly because tools like pyo3 make it as easy as passing a flag) for the forward compatibility guarantee. Do `abi3` wheels actually need to break? Ideally, I’d like to see:

- `abi3` continues to provide forward compatibility with non-free-threaded Python
- `abi4` introduced to cover both free-threaded and GIL-enabled builds, with the same forward compatibility guarantee

---

<div class="post-metadata">

### Author: ![philthompson](https://avatars.discourse-cdn.com/v4/letter/p/ed8c4c/32.png) [@philthompson](https://discuss.python.org/u/philthompson)
#### Post date: [September 9, 2025, 10:55am UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/18 "2025-09-09T10:55:17Z")

</div>

I hadn’t realised that there would continue to be separate Python  
builds.

---

<div class="post-metadata">

### Author: ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)
#### Post date: [September 9, 2025, 12:01pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/19 "2025-09-09T12:01:53Z")

</div>

> [@pitrou](#):
>
> The PEP should definitely be explicit on this IMHO.

To some extent that’s a question for tools (the packaging library in particular). There’s no standard that says “CPython 3.15 must support `cp39-abi3` tagged wheels”. But yes, the PEP needs to be explicit about what it expects from tag semantics, and that’s not as simple as it looks.

> [@encukou](#):
>
> But for free-threading, the stable ABI needs to be broken in _some_ way.

And in my view, that breakage should come with a version bump. I don’t think that these days anyone associates “abi3” with “Python 3”, so there’s no implication that “abi4” would be in any way like the Python 2-\>3 transition.

> [@encukou](#):
>
> Yes, but only in the regular (non-free-threaded) builds of 3.15+.

I don’t know what the proposal is for platform tags for free-threaded Python builds. Has anyone made a formal proposal on this yet? I have no feel for whether free threaded CPython 3.15 should accept `cp315` wheels, or conversely whether CPython 3.15 with the GIL should accept `cp315t` wheels. And it’s important to note that generally, tags are considered _separately_, so “it depends on whether it’s using `abi3`” isn’t a helpful answer here.

---

<div class="post-metadata">

### Author: ![pitrou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pitrou/32/28_2.png) [@pitrou](https://discuss.python.org/u/pitrou)
#### Post date: [September 9, 2025, 12:16pm UTC](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/20 "2025-09-09T12:16:22Z")

</div>

> [@pf\_moore](#):
>
> To some extent that’s a question for tools (the packaging library in particular). There’s no standard that says “CPython 3.15 must support `cp39-abi3` tagged wheels”.

That’s a weird statement. Is it a packaging tool’s job to say whether CPython 3.15 is compatible with Stable ABI 3.9?

[Next page](https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628.md?page=2)
