@@ -14,11 +14,17 @@ Post-History: `06-Jan-2026 <https://discuss.python.org/t/105552>`__
1414Abstract
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
2026The 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
2228documented 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),
4349allowing future CPython versions (or even alternative implementations) to
4450change 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
5157In :pep: `793 `, new module creation API was added.
5258Instead 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
8187Limited forward compatibility
@@ -140,6 +146,8 @@ near future, users could add it with an "``OPTIONAL``" flag, making their class
140146support the ``@ `` operator only on CPython versions with that operator.
141147
142148
149+ .. _pep820-rationale :
150+
143151Rationale
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.
228236Fixed-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
232240flags.
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,
234242but it would silently work on common platforms.
235243Using ``int `` but avoiding values over ``UINT16_MAX `` wastes 16 bits
236244on 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
241247Memory layout
242248-------------
243249
244250On common 64-bit platforms, we can keep the size of the new struct the same
245251as the existing ``PyType_Slot `` and ``PyModuleDef_Slot ``. (The existing
246252struct 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.)
248254On 32-bit platforms, this proposal calls for the same layout as on 64-bit,
249255doubling the size compared to the existing structs (from 8 bytes to 16).
250256For “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
283289or 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+
286313Specification
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
317344The following function will be added.
318345It will create the corresponding Python type object from the given
319346array 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+
323357The ``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
329369General 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
406446We'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
416460Nested 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
429473Each ``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} ``,
431475and similar with ``PyModuleDef_Slot ``.
432476
433477The 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
444486New 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
460503The following new slot IDs will be added to cover existing
461504members of ``PyModuleDef ``:
@@ -483,6 +526,9 @@ Specifying both in a single definition will be deprecated (currently,
483526None 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
487533Slot renumbering
488534----------------
@@ -496,7 +542,7 @@ Slots numbered 1 through 4 (``Py_bf_getbuffer``...\ ``Py_mp_length`` and
496542The old numbers will remain as aliases, and will be used when compiling for
497543Stable 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
501547unique IDs:
502548
@@ -532,10 +578,48 @@ in this PEP.
532578This 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+
535605Backwards 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
541625Security Implications
@@ -553,13 +637,69 @@ Adjust the "Extending and Embedding" tutorial to use this.
553637Reference 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
559644Rejected 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
565705Open Issues
@@ -568,6 +708,13 @@ Open Issues
568708None 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+
571718Copyright
572719=========
573720
0 commit comments