Skip to content

Commit 8c68109

Browse files
authored
PEP 820: Changes based on discussion & implementation (GH-4788)
1 parent 5f1edbb commit 8c68109

File tree

1 file changed

+179
-32
lines changed

1 file changed

+179
-32
lines changed

peps/pep-0820.rst

Lines changed: 179 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@ Post-History: `06-Jan-2026 <https://discuss.python.org/t/105552>`__
1414
Abstract
1515
========
1616

17-
Replace type and module slots with a new, more type-safe structure that allows
18-
adding new slots in a more forward-compatible way.
17+
Replace type and module slots with a new structure: a tagged anonymous
18+
union with flags.
19+
This improves type safety and allows adding new slots
20+
in a more forward-compatible way.
21+
22+
API added in 3.15 (:external+py3.15:c:func:`PyModule_FromSlotsAndSpec` and the
23+
new :external+py3.15:ref:`extension export hook <extension-export-hook>`)
24+
will be changed to use the new slots.
1925

2026
The existing slot structures and related API is soft-deprecated.
21-
(That is: it will continue to work without warnings, and it’ll be fully
27+
(That is: they will continue to work without warnings, and it’ll be fully
2228
documented and supported, but we plan to not add any new features to it.)
2329

2430

@@ -43,10 +49,10 @@ structure of the object to stay opaque (in both the API and the ABI),
4349
allowing future CPython versions (or even alternative implementations) to
4450
change the details.
4551

46-
Both structures contain a *slots* field essentially an array of
47-
`tagged unions <https://en.wikipedia.org/wiki/Tagged_union>`__,
48-
which allows for future expansion.
49-
(In practice, slots are ``void`` pointers taged with an ``int`` ID.)
52+
Both structures contain a *slots* field, essentially an array of
53+
`tagged unions <https://en.wikipedia.org/wiki/Tagged_union>`__
54+
(``void`` pointers taged with an ``int`` ID).
55+
This allows for future expansion.
5056

5157
In :pep:`793`, new module creation API was added.
5258
Instead of the ``PyModuleDef`` structure, it uses only an array of *slots*.
@@ -75,7 +81,7 @@ Type safety
7581
but is technically undefined or implementation-defined behaviour in C.
7682

7783
For example: :c:macro:`Py_tp_doc` marks a string; :c:macro:`Py_mod_gil`
78-
an integer, and :c:macro:`Py_tp_repr` a function; all must
84+
a small integer, and :c:macro:`Py_tp_repr` a function; all must
7985
be cast to ``void*``.
8086

8187
Limited forward compatibility
@@ -140,6 +146,8 @@ near future, users could add it with an "``OPTIONAL``" flag, making their class
140146
support the ``@`` operator only on CPython versions with that operator.
141147

142148

149+
.. _pep820-rationale:
150+
143151
Rationale
144152
=========
145153

@@ -205,9 +213,9 @@ This complicates slot handling inside the interpreter, but allows:
205213

206214
- Mixing dynamically allocated (or stack-allocated) slots with ``static`` ones.
207215
This solves the issue that lead to the ``PyType_From*`` family of
208-
functions expanding with values that typically can't be ``static``
209-
(i.e. it's often a symbol from another DLL, which can't be ``static``
210-
data on Windows).
216+
functions expanding with values that typically can't be ``static``.
217+
For example, the *module* argument to :c:func:`PyType_FromModuleAndSpec`
218+
should be a heap-allocated module object.
211219
- Sharing a subset of the slots to implement functionality
212220
common to several classes/modules.
213221
- Easily including some slots conditionally, e.g. based on the Python version.
@@ -228,23 +236,21 @@ and only use the “new” slots if they need any new features.
228236
Fixed-width integers
229237
---------------------
230238

231-
This proposal uses fixed-width integers (``uint16_t``), for slot IDs and
239+
This proposal uses fixed-width integers (``uint16_t``) for slot IDs and
232240
flags.
233-
With the C ``int`` type, using more that 16 bits would not be portable,
241+
With the C ``int`` type, using more than 16 bits would not be portable,
234242
but it would silently work on common platforms.
235243
Using ``int`` but avoiding values over ``UINT16_MAX`` wastes 16 bits
236244
on common platforms.
237245

238-
With these defined as ``uint16_t``, it seems natural to use fixed-width
239-
integers for everything except pointers and sizes.
240246

241247
Memory layout
242248
-------------
243249

244250
On common 64-bit platforms, we can keep the size of the new struct the same
245251
as the existing ``PyType_Slot`` and ``PyModuleDef_Slot``. (The existing
246252
struct waste 6 out of 16 bytes due to ``int`` portability and padding;
247-
this proposal puts those bits to use for new features.)
253+
this proposal puts some of those bits to use for new features.)
248254
On 32-bit platforms, this proposal calls for the same layout as on 64-bit,
249255
doubling the size compared to the existing structs (from 8 bytes to 16).
250256
For “configuration” data that's usually ``static``, it should be OK.
@@ -283,6 +289,27 @@ The main disadvantage is that any internal lookup tables will be either bigger
283289
or harder to manage (if they're merged).
284290

285291

292+
Deprecation warnings
293+
--------------------
294+
295+
Multiple slots are documented to not allow NULL values, but CPython allows
296+
NULL for backwards compatibility.
297+
Similarly, multiple slot IDs should not appear more than once in a single
298+
array, but CPython allows such duplicates.
299+
300+
This is a maintenance issue, as CPython should preserve its undocumented
301+
(and often untested) behaviour in these cases as the implementation is changed.
302+
303+
It also prevents API extensions.
304+
For example, instead of adding the :c:macro:`Py_TPFLAGS_DISALLOW_INSTANTIATION`
305+
flag in 3.10, we could have allowed settning the ``Py_tp_new`` slot to NULL for
306+
the same effect.
307+
308+
To allow changing the edge case behaviour in the (far) future,
309+
and to allow freedom for possible alternative implementations of the C API,
310+
we'll start issuing runtime deprecation warnings in these cases.
311+
312+
286313
Specification
287314
=============
288315

@@ -311,20 +338,33 @@ A new ``PySlot`` structure will be defined as follows::
311338
- An union with the data, whose type depends on the slot.
312339

313340

314-
Functions that use slots
315-
------------------------
341+
New API
342+
-------
316343

317344
The following function will be added.
318345
It will create the corresponding Python type object from the given
319346
array of slots::
320347

321348
PyObject *PyType_FromSlots(const PySlot *slots);
322349

350+
With this function, the ``Py_tp_token`` slot may not be set to
351+
``Py_TP_USE_SPEC`` (i.e. ``NULL``).
352+
353+
354+
Changed API
355+
-----------
356+
323357
The ``PyModule_FromSlotsAndSpec`` function (added in CPython 3.15 in
324358
:pep:`793`) will be *changed* to take the new slot structure::
325359

326360
PyObject *PyModule_FromSlotsAndSpec(const PySlot *slots, PyObject *spec)
327361

362+
The :external+py3.15:ref:`extension module export hook <extension-export-hook>`
363+
added in :pep:`793` (:samp:`PyModExport_{<name>}`) will be *changed* to
364+
return the new slot structure.
365+
The :external+py3.15:c:macro:`PyMODEXPORT_FUNC` macro will
366+
be updated accordingly.
367+
328368

329369
General slot semantics
330370
----------------------
@@ -354,7 +394,7 @@ Flags
354394
This flag is implied for function pointers.
355395

356396
The flag applies even to data the slot points to "indirectly", except for
357-
nested slots -- see ``Py_slot_subslots`` below -- which can have their
397+
nested slots -- see :ref:`pep820-nested-tables` below -- which can have their
358398
own ``PySlot_STATIC`` flag.
359399
For example, if applied to a ``Py_tp_members`` slot that points to an
360400
*array* of ``PyMemberDef`` structures, then the entire array, as well as the
@@ -371,7 +411,7 @@ Flags
371411
If the entire block is to be optional, it should end with a
372412
slot with the OPTIONAL flag.
373413

374-
- ``PySlot_IS_PTR``: The data is stored in ``sl_ptr``, and must be cast to
414+
- ``PySlot_INTPTR``: The data is stored in ``sl_ptr``, and must be cast to
375415
the appropriate type.
376416

377417
This flag simplifies porting from the existing ``PyType_Slot`` and
@@ -404,14 +444,18 @@ The following macros will be added to the API to simplify slot definition::
404444
#define PySlot_END {0}
405445

406446
We'll also add two more macros that avoid named initializers,
407-
for use in C++11-compatibile code::
447+
for use in C++11-compatibile code.
448+
Note that these cast the value to ``void*``, so they do not improve type safety
449+
over existing slots::
408450

409451
#define PySlot_PTR(NAME, VALUE) \
410-
{NAME, PySlot_IS_PTR, {0}, {(void*)(VALUE)}}
452+
{NAME, PySlot_INTPTR, {0}, {(void*)(VALUE)}}
411453

412454
#define PySlot_PTR_STATIC(NAME, VALUE) \
413-
{NAME, PySlot_IS_PTR|Py_SLOT_STATIC, {0}, {(void*)(VALUE)}}
455+
{NAME, PySlot_INTPTR|Py_SLOT_STATIC, {0}, {(void*)(VALUE)}}
456+
414457

458+
.. _pep820-nested-tables:
415459

416460
Nested slot tables
417461
------------------
@@ -427,7 +471,7 @@ Two more slots will allow similar nesting for existing slot structures:
427471
- ``Py_mod_slots`` for an array of ``PyModuleDef_Slot``
428472

429473
Each ``PyType_Slot`` in the array will be converted to
430-
``(PySlot){.sl_id=slot, .sl_flags=PySlot_IS_PTR, .sl_ptr=func}``,
474+
``(PySlot){.sl_id=slot, .sl_flags=PySlot_INTPTR, .sl_ptr=func}``,
431475
and similar with ``PyModuleDef_Slot``.
432476

433477
The initial implementation will have restrictions that may be lifted
@@ -437,8 +481,6 @@ in the future:
437481
``PySlot_HAS_FALLBACK`` (the flag cannot be set on them nor a slot that
438482
precedes them).
439483
- Nesting depth will be limited to 5 levels.
440-
(4 levels for the existing ``PyType_From*``, ``PyModule_From*`` functions,
441-
which will use up one level internally.)
442484

443485

444486
New slot IDs
@@ -454,8 +496,9 @@ definitions, will be added:
454496
allowed with ``Py_slot_end``.
455497

456498
- ``Py_slot_subslots``, ``Py_tp_slots``, ``Py_mod_slots``: see
457-
*Nested slot tables* above
458-
- ``Py_slot_invalid``: treated as an unknown slot ID.
499+
:ref:`pep820-nested-tables` above
500+
- ``Py_slot_invalid`` (defined as ``UINT16_MAX``, i.e. ``-1``): treated as an
501+
unknown slot ID.
459502

460503
The following new slot IDs will be added to cover existing
461504
members of ``PyModuleDef``:
@@ -483,6 +526,9 @@ Specifying both in a single definition will be deprecated (currently,
483526
None of the new slots will be usable with ``PyType_GetSlot``.
484527
(This limitation may be lifted in the future, with C API WG approval.)
485528

529+
Of the new slots, only ``Py_slot_end``, ``Py_slot_subslots``, ``Py_tp_slots``,
530+
``Py_mod_slots`` will be allowed in ``PyType_Spec`` and/or ``PyModuleDef``.
531+
486532

487533
Slot renumbering
488534
----------------
@@ -496,7 +542,7 @@ Slots numbered 1 through 4 (``Py_bf_getbuffer``...\ ``Py_mp_length`` and
496542
The old numbers will remain as aliases, and will be used when compiling for
497543
Stable ABI versions below 3.15.
498544

499-
Slots for members of ``PyType_Spec``, which were added in
545+
Slots for members of ``PyModuleDef``, which were added in
500546
:ref:`PEP 793 <pep793-api-summary>`, will be renumbered so that they have
501547
unique IDs:
502548

@@ -532,10 +578,48 @@ in this PEP.
532578
This includes nested "new-style" slots (``Py_slot_subslots``).
533579

534580

581+
.. _pep820-hard-deprecations:
582+
583+
Deprecation warnings
584+
--------------------
585+
586+
CPython will emit runtime deprecation warnings for the following cases,
587+
for slots where the case is currently disallowed in documentation but allowed
588+
by the runtime:
589+
590+
- setting a slot value to NULL:
591+
592+
- all type slots except ``Py_tp_doc``
593+
- ``Py_mod_create``
594+
- ``Py_mod_exec``
595+
596+
- repeating a slot ID in a single slots array (including sub-slot arrays
597+
added in this PEP):
598+
599+
- all type slots, except slots where this is already a runtime error
600+
(``Py_tp_doc``, ``Py_tp_members``)
601+
- ``Py_mod_create``
602+
- ``Py_mod_abi``
603+
604+
535605
Backwards Compatibility
536606
=======================
537607

538-
This PEP only adds APIs, so it's backwards compatible.
608+
This PEP proposes to change API that was already released in alpha versions of
609+
Python 3.15.
610+
This will inconvenience early adopters of that API, but -- as long as the
611+
PEP is accepted and implemented before the first bety -- this change is within
612+
the letter and spirit of our backwards compatibility policy.
613+
614+
Renumbering of slots is done in a backwards-compatible way.
615+
Old values continue to be accepted, and are used when compiling for
616+
earlier Stable ABI.
617+
618+
Some cases that are documented as illegal will begin emitting deprecation
619+
warnings (see :ref:`pep820-hard-deprecations`).
620+
621+
Otherwise, this PEP only adds and soft-deprecates APIs, which is backwards
622+
compatible.
539623

540624

541625
Security Implications
@@ -553,13 +637,69 @@ Adjust the "Extending and Embedding" tutorial to use this.
553637
Reference Implementation
554638
========================
555639

556-
None yet.
640+
Draft implementation is available as `pull request #37 in the author's fork
641+
<https://github.com/encukou/cpython/pull/37>`__.
557642

558643

559644
Rejected Ideas
560645
==============
561646

562-
None yet.
647+
See the :ref:`pep820-rationale` section for several alternative ideas.
648+
649+
Third-party slot ID allocation
650+
------------------------------
651+
652+
It was suggested to allow third parties to reserve slot IDs for their own use.
653+
This would be mainly useful for alternate implementations. For example,
654+
something like GraalPy might want custom type slots (e.g. an "inherits
655+
from this Java class" slot).
656+
Similarly, at one point PyPy had an extra ``tp_pypy_flags`` in their
657+
typeobject struct.
658+
659+
This PEP does not specify a namespace mechanism.
660+
One can be added in the future.
661+
We're also free to reserve individual slot IDs for alternate implementations.
662+
663+
Note that slots are not a good way for *extension modules* to add extra data
664+
to types or modules, as there is no API to retrieve the slots used to create
665+
a specific object.
666+
667+
Avoiding anonymous unions
668+
-------------------------
669+
670+
This PEP proposes a struct with *anonymous unions*, which are not yet used in
671+
CPython's documented public API.
672+
673+
There is no known issue with adding these, but the following notes may
674+
be relevant:
675+
676+
- Anonymous unions are only supported in C since C11.
677+
But, CPython already requires the feature, and uses it for internal members
678+
of the ``PyObject`` struct.
679+
680+
- Until C++20, which adds C-style designated initializers, C++ initializers
681+
only allow setting the first member of a union.
682+
However, this is an issue for *named* unions as well.
683+
Avoiding unions entirely would mean losing most of the type-safety
684+
improvements of this PEP.
685+
686+
Note that the proposed flag ``PySlot_INTPTR``, and the workaround macros
687+
``PySlot_PTR`` & ``PySlot_PTR_STATIC``, allow using this API in
688+
code that needs to be compatible with C++11 or has similar union-related
689+
limitations.
690+
691+
- C++ doesn't have anonymous *structs*.
692+
This might surprise C programmers for whom anonymous structs/unions are
693+
a single language feature.
694+
695+
- Non-C/C++ language wrappers may need to give the union a name.
696+
This is fine.
697+
(Dear reader: if you need this, please open a CPython issue about
698+
exposing a preferred name in headers and documentation.)
699+
700+
For a bigger picture: anonymous unions can be a helpful tool for implemeting
701+
tagged unions and for evolving public API in backwards-compatible ways.
702+
This PEP intentionally opens the door to using them more often.
563703

564704

565705
Open Issues
@@ -568,6 +708,13 @@ Open Issues
568708
None yet.
569709

570710

711+
Acknowledgements
712+
================
713+
714+
Thanks to Da Woods, Antoine Pitrou and Mark Shannon
715+
for substantial input on this iteration of the proposal.
716+
717+
571718
Copyright
572719
=========
573720

0 commit comments

Comments
 (0)