Skip to content

Commit 4cc7ea2

Browse files
committed
PEP 793, 803, 820: Update & mark as Final
1 parent c7fb679 commit 4cc7ea2

5 files changed

Lines changed: 184 additions & 87 deletions

File tree

peps/pep-0793.rst

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ PEP: 793
22
Title: PyModExport: A new entry point for C extension modules
33
Author: Petr Viktorin <encukou@gmail.com>
44
Discussions-To: https://discuss.python.org/t/93444
5-
Status: Accepted
5+
Status: Final
66
Type: Standards Track
77
Created: 23-May-2025
88
Python-Version: 3.15
99
Post-History: `14-Mar-2025 <https://discuss.python.org/t/84498/>`__,
1010
`27-May-2025 <https://discuss.python.org/t/93444/>`__,
1111
Resolution: `23-Oct-2025 <https://discuss.python.org/t/93444/46>`__
1212

13+
.. canonical-doc:: :ref:`py3.15:extension-modules`
14+
1315

1416
Abstract
1517
========
@@ -21,7 +23,7 @@ This allows extension authors to avoid using a statically allocated
2123
``PyObject``, lifting the most common obstacle to making one compiled library
2224
file usable with both regular and free-threaded builds of CPython.
2325

24-
To make this viable, we also specify new module slot types to replace
26+
To make this viable, we also specify new module slot IDs to replace
2527
``PyModuleDef``'s fields, and to allow adding a *token* similar to the
2628
``Py_tp_token`` used for type objects.
2729

@@ -131,8 +133,8 @@ This proposal does away with fixed fields and proposes using a slots array
131133
directly, without a wrapper struct.
132134

133135
The ``PyModuleDef_Slot`` struct does have some downsides compared to fixed fields.
134-
We believe these are fixable, but leave that out of scope of this PEP
135-
(see “Improving slots in general” in the Possible Future Directions section).
136+
We believe these are fixable, but leave that out of scope of this PEP.
137+
(Note: this was done in :pep:`820`, still in Python 3.15.)
136138

137139

138140
Tokens
@@ -187,6 +189,8 @@ like this:
187189
188190
PyModuleDef_Slot *PyModExport_<NAME>(void);
189191
192+
.. note:: :pep:`820` changed the return type to ``PySlot *``.
193+
190194
where ``<NAME>`` is the name of the module.
191195
For non-ASCII names, it will instead look for ``PyModExportU_<NAME>``,
192196
with ``<NAME>`` encoded as for existing ``PyInitU_*`` hooks
@@ -223,14 +227,13 @@ A new function will be added to create a module from an array of slots:
223227
224228
PyObject *PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *slots, PyObject *spec)
225229
230+
.. note:: :pep:`820` changed the first argument type to ``PySlot *``.
231+
226232
The *slots* argument must point to an array of ``PyModuleDef_Slot`` structures,
227233
terminated by a slot with ``slot=0`` (typically written as ``{0}`` in C).
228-
There are no required slots, though *slots* must not be ``NULL``.
229-
It follows that minimal input contains only the terminator slot.
230-
231-
.. note::
232-
233-
If :pep:`803` is accepted, the ``Py_mod_abi`` slot will be mandatory.
234+
The ``Py_mod_abi`` slot is required (see :pep:`803`); all other slots
235+
are optional.
236+
It follows that *slots* must not be ``NULL``.
234237

235238
The *spec* argument is a duck-typed ModuleSpec-like object, meaning that any
236239
attributes defined for ``importlib.machinery.ModuleSpec`` have matching
@@ -373,7 +376,7 @@ Bits & Pieces
373376
-------------
374377

375378
A ``PyMODEXPORT_FUNC`` macro will be added, similar to the ``PyMODINIT_FUNC``
376-
macro but with ``PyModuleDef_Slot *`` as the return type.
379+
macro but with ``PySlot *`` as the return type.
377380

378381
A ``PyModule_GetStateSize`` function will be added to retrieve the size set
379382
by ``Py_mod_state_size`` or ``PyModuleDef.m_size``.
@@ -397,6 +400,9 @@ The ``PyInit_*`` export hook will be
397400

398401
New API summary
399402
---------------
403+
404+
.. note:: This summary was updated with a change from :pep:`820`.
405+
400406
Python will load a new module export hook, with two variants:
401407

402408
.. code-block:: c
@@ -408,7 +414,7 @@ The following functions will be added:
408414

409415
.. code-block:: c
410416
411-
PyObject *PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *, PyObject *spec)
417+
PyObject *PyModule_FromSlotsAndSpec(const PySlot *, PyObject *spec)
412418
int PyModule_Exec(PyObject *)
413419
int PyModule_GetToken(PyObject *, void**)
414420
PyObject *PyType_GetModuleByToken(PyTypeObject *type, const void *token)
@@ -477,6 +483,15 @@ Here is a guide to convert an existing module to the new API, including
477483
some tricky edge cases.
478484
It should be moved to a HOWTO in the documentation.
479485

486+
.. note::
487+
488+
The guide is available at :ref:`py3.15:abi3t-howto-modexport`.
489+
(It is part of the ``abi3t`` migration HOWTO since switching to
490+
``PyModExport`` doesn't bring benefits in 3.15 if you don't also
491+
adopt ``abi3t``.)
492+
493+
This section contains the original, outdated guide.
494+
480495
This guide is meant for hand-written modules. For code generators and language
481496
wrappers, the :ref:`pep793-shim` below may be more useful.
482497

@@ -585,24 +600,29 @@ The following implementation can be copied and pasted to a project; only the
585600
names ``PyInit_examplemodule`` (twice) and ``PyModExport_examplemodule`` should
586601
need adjusting.
587602

588-
When added to the :ref:`pep793-example` below and compiled with a
589-
non-free-threaded build of this PEP's reference implementation, the resulting
590-
extension is compatible with non-free-threading 3.9+ builds, in addition to a
603+
.. note::
604+
605+
This section was updated for :pep:`820`.
606+
607+
When compiled together with the :ref:`pep793-example` below on a
608+
non-free-threaded build of Python 3.15, the resulting
609+
extension is compatible with non-free-threading 3.11+ builds, in addition to a
591610
free-threading build of the reference implementation.
592611
(The module must be named without a version tag, e.g. ``examplemodule.so``,
593612
and be placed on ``sys.path``.)
594613

595614
Full support for creating such modules will require backports of some new
596615
API, and support in build/install tools. This is out of scope of this PEP.
597616
(In particular, the demo “cheats” by using a subset of Limited API 3.15 that
598-
*happens to work* on 3.9; a proper implementation would use Limited API 3.9
599-
with backport shims for new API like ``Py_mod_name``.)
617+
*happens to work* on 3.11, and includes a few hacks.
618+
A proper implementation would use Limited API 3.11 with cleaner backport shims
619+
for new API like ``Py_mod_name``.)
600620

601621
This implementation places a few additional requirements on the slots array:
602622

603-
- Slots that correspond to ``PyModuleDef`` members must come first.
623+
- ``Py_mod_slots`` and ``Py_slot_subslots`` are not supported.
604624
- A ``Py_mod_name`` slot is required.
605-
- Any ``Py_mod_token`` must be set to ``&module_def_and_token``, defined here.
625+
- Any ``Py_mod_token`` must be set to the ``MOD_TOKEN`` defined here.
606626

607627
.. literalinclude:: pep-0793/shim.c
608628
:language: c
@@ -626,6 +646,10 @@ be added as a new HOWTO.
626646
Example
627647
=======
628648

649+
.. note::
650+
651+
The example was updated for :pep:`820`.
652+
629653
.. literalinclude:: pep-0793/examplemodule.c
630654
:language: c
631655

@@ -693,6 +717,10 @@ These ideas are out of scope for *this* proposal.
693717
Improving slots in general
694718
--------------------------
695719

720+
.. note::
721+
722+
This idea was implemented in :pep:`820`.
723+
696724
Slots -- and specifically the existing ``PyModuleDef_Slot`` -- do have a few
697725
shortcomings. The most important are:
698726

peps/pep-0793/examplemodule.c

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ typedef struct {
3535
int value;
3636
} examplemodule_state;
3737

38-
static PyModuleDef_Slot examplemodule_slots[];
38+
static PySlot examplemodule_slots[];
39+
40+
#ifndef MOD_TOKEN
41+
// Module token: normally set to the slots array,
42+
// but a backwards-compatibility shim will redefine it.
43+
#define MOD_TOKEN (&examplemodule_slots)
44+
#endif
3945

4046
// increment_value function
4147

@@ -59,20 +65,21 @@ exampletype_repr(PyObject *self)
5965
{
6066
/* To get module state, we cannot use PyModule_GetState(Py_TYPE(self)),
6167
* since Py_TYPE(self) might be a subclass defined in an unrelated module.
62-
* So, use PyType_GetModuleByToken.
68+
* So, we should use use PyType_GetModuleByToken.
69+
* For pre-3.15 compatibility, we use PyType_GetModuleByDef instead:
70+
* this needs a cast and returns a borrowed reference.
6371
*/
64-
PyObject *module = PyType_GetModuleByToken(
65-
Py_TYPE(self), examplemodule_slots);
72+
PyObject *module = PyType_GetModuleByDef(
73+
Py_TYPE(self), (PyModuleDef*)MOD_TOKEN);
6674
if (!module) {
6775
return NULL;
6876
}
6977
examplemodule_state *state = PyModule_GetState(module);
70-
Py_DECREF(module);
7178
if (!state) {
7279
return NULL;
7380
}
74-
return PyUnicode_FromFormat("<%T object; module value = %d>",
75-
self, state->value);
81+
return PyUnicode_FromFormat("<ExampleType object; module value = %d>",
82+
state->value);
7683
}
7784

7885
static PyType_Spec exampletype_spec = {
@@ -105,13 +112,17 @@ examplemodule_exec(PyObject *module) {
105112

106113
PyDoc_STRVAR(examplemodule_doc, "Example extension.");
107114

108-
static PyModuleDef_Slot examplemodule_slots[] = {
109-
{Py_mod_name, "examplemodule"},
110-
{Py_mod_doc, (char*)examplemodule_doc},
111-
{Py_mod_methods, examplemodule_methods},
112-
{Py_mod_state_size, (void*)sizeof(examplemodule_state)},
113-
{Py_mod_exec, (void*)examplemodule_exec},
114-
{0}
115+
PyABIInfo_VAR(abi_info);
116+
117+
static PySlot examplemodule_slots[] = {
118+
PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
119+
PySlot_STATIC_DATA(Py_mod_name, "examplemodule"),
120+
PySlot_STATIC_DATA(Py_mod_doc, (char*)examplemodule_doc),
121+
PySlot_STATIC_DATA(Py_mod_methods, examplemodule_methods),
122+
PySlot_SIZE(Py_mod_state_size, sizeof(examplemodule_state)),
123+
PySlot_FUNC(Py_mod_exec, examplemodule_exec),
124+
PySlot_STATIC_DATA(Py_mod_token, MOD_TOKEN),
125+
PySlot_END
115126
};
116127

117128
// Avoid "implicit declaration of function" warning:

peps/pep-0793/shim.c

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,91 @@
1-
#include <string.h> // memset
1+
#include <Python.h>
22

3-
PyMODINIT_FUNC PyInit_examplemodule(void);
3+
// Hack: Restore old definition of Py_TYPE
4+
#undef Py_TYPE
5+
#define Py_TYPE(OBJ) (((PyObject*)OBJ)->ob_type)
46

7+
// PyModuleDef, also reused as module token
58
static PyModuleDef module_def_and_token;
9+
#define MOD_TOKEN (&module_def_and_token)
10+
11+
#include "examplemodule.c"
12+
13+
extern PySlot *PyModExport_examplemodule(void);
14+
15+
PyMODINIT_FUNC PyInit_examplemodule(void);
616

717
PyMODINIT_FUNC
818
PyInit_examplemodule(void)
919
{
10-
PyModuleDef_Slot *slot = PyModExport_examplemodule();
11-
1220
if (module_def_and_token.m_name) {
1321
// Take care to only set up the static PyModuleDef once.
1422
// (PyModExport might theoretically return different data each time.)
1523
return PyModuleDef_Init(&module_def_and_token);
1624
}
17-
int copying_slots = 1;
18-
for (/* slot set above */; slot->slot; slot++) {
19-
switch (slot->slot) {
25+
26+
static PyModuleDef_Slot module_slots[5] = {{0}};
27+
module_def_and_token.m_slots = module_slots;
28+
int current_m_slot = 0;
29+
30+
PySlot *slot = PyModExport_examplemodule();
31+
32+
for (/* slot set above */; slot->sl_id; slot++) {
33+
switch (slot->sl_id) {
2034
// Set PyModuleDef members from slots. These slots must come first.
21-
# define COPYSLOT_CASE(SLOT, MEMBER, TYPE) \
22-
case SLOT: \
23-
if (!copying_slots) { \
24-
PyErr_SetString(PyExc_SystemError, \
25-
#SLOT " must be specified earlier"); \
26-
goto error; \
27-
} \
28-
module_def_and_token.MEMBER = (TYPE)(slot->value); \
29-
break; \
30-
/////////////////////////////////////////////////////////////////
31-
COPYSLOT_CASE(Py_mod_name, m_name, char*)
32-
COPYSLOT_CASE(Py_mod_doc, m_doc, char*)
33-
COPYSLOT_CASE(Py_mod_state_size, m_size, Py_ssize_t)
34-
COPYSLOT_CASE(Py_mod_methods, m_methods, PyMethodDef*)
35-
COPYSLOT_CASE(Py_mod_state_traverse, m_traverse, traverseproc)
36-
COPYSLOT_CASE(Py_mod_state_clear, m_clear, inquiry)
37-
COPYSLOT_CASE(Py_mod_state_free, m_free, freefunc)
35+
# define COPYSLOT_CASE(SLOT, DEF_MEMBER, SL_MEMBER, TYPE) \
36+
case SLOT: \
37+
if (slot->sl_flags & PySlot_INTPTR) { \
38+
module_def_and_token.DEF_MEMBER = (TYPE)(slot->sl_ptr); \
39+
} else { \
40+
module_def_and_token.DEF_MEMBER = (TYPE)(slot->SL_MEMBER);\
41+
} \
42+
break; \
43+
///////////////////////////////////////////////////////////////////
44+
COPYSLOT_CASE(Py_mod_name, m_name, sl_ptr, char*)
45+
COPYSLOT_CASE(Py_mod_doc, m_doc, sl_ptr, char*)
46+
COPYSLOT_CASE(Py_mod_state_size, m_size, sl_size, Py_ssize_t)
47+
COPYSLOT_CASE(Py_mod_methods, m_methods, sl_ptr, PyMethodDef*)
48+
COPYSLOT_CASE(Py_mod_state_traverse, m_traverse, sl_func, traverseproc)
49+
COPYSLOT_CASE(Py_mod_state_clear, m_clear, sl_func, inquiry)
50+
COPYSLOT_CASE(Py_mod_state_free, m_free, sl_func, freefunc)
51+
COPYSLOT_CASE(Py_mod_slots, m_slots, sl_ptr, PyModuleDef_Slot*)
52+
# undef COPYSLOT_CASE
53+
case Py_mod_create:
54+
case Py_mod_exec:
55+
case Py_mod_multiple_interpreters:
56+
case Py_mod_gil:
57+
int old_slot_id = (int)slot->sl_id;
58+
if (old_slot_id > 83) {
59+
// Hack: slots were renumbered; use old IDs here
60+
old_slot_id -= 83;
61+
}
62+
module_slots[current_m_slot].slot = old_slot_id;
63+
module_slots[current_m_slot].value = slot->sl_ptr;
64+
current_m_slot++;
65+
if (current_m_slot >= 4) {
66+
PyErr_SetString(PyExc_SystemError,
67+
"Too many slots for array");
68+
goto error;
69+
}
70+
break;
3871
case Py_mod_token:
3972
// With PyInit_, the PyModuleDef is used as the token.
40-
if (slot->value != &module_def_and_token) {
73+
if (slot->sl_ptr != &module_def_and_token) {
4174
PyErr_SetString(PyExc_SystemError,
4275
"Py_mod_token must be set to "
4376
"&module_def_and_token");
4477
goto error;
4578
}
4679
break;
80+
case Py_mod_abi:
81+
// ABI checking skipped here
82+
break;
4783
default:
48-
// The remaining slots become m_slots in the def.
49-
// (`slot` now points to the "rest" of the original
50-
// zero-terminated array.)
51-
if (copying_slots) {
52-
module_def_and_token.m_slots = slot;
84+
if (!(slot->sl_flags & PySlot_OPTIONAL)) {
85+
PyErr_Format(PyExc_SystemError,
86+
"Unknown slot ID %d.", (int)slot->sl_id);
87+
goto error;
5388
}
54-
copying_slots = 0;
5589
break;
5690
}
5791
}
@@ -63,6 +97,6 @@ PyInit_examplemodule(void)
6397
return PyModuleDef_Init(&module_def_and_token);
6498

6599
error:
66-
memset(&module_def_and_token, 0, sizeof(module_def_and_token));
100+
module_def_and_token.m_name = NULL;
67101
return NULL;
68102
}

0 commit comments

Comments
 (0)