Skip to content

Commit 9f5ea5e

Browse files
encukouhugovk
andauthored
PEP 793: Updates from discussion & implementation (#4453)
* Add a backwards-compatibility shim for PyInit * Soft-deprecate the old API, and make PyType_GetModuleByDef take token * Apply suggestions from code review Co-authored-by: Hugo van Kemenade <[email protected]> --------- Co-authored-by: Hugo van Kemenade <[email protected]>
1 parent a17c3e6 commit 9f5ea5e

File tree

4 files changed

+186
-13
lines changed

4 files changed

+186
-13
lines changed

peps/pep-0387.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ Basic policy for backwards compatibility
114114
platforms).
115115

116116

117+
.. _pep387-soft-deprecation:
118+
117119
Soft Deprecation
118120
================
119121

peps/pep-0793.rst

Lines changed: 115 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ To make this viable, we also specify new module slot types to replace
2626

2727
We also add an API for defining modules from slots dynamically.
2828

29+
The existing API (``PyInit_*``) is soft-deprecated.
30+
(That is: it will continue to work without warnings, and it'll be fully
31+
documented and supported, but we plan to not add any new features to it.)
32+
2933

3034
Background & Motivation
3135
=======================
@@ -147,6 +151,27 @@ Unlike types, the import mechanism often has a pointer that's known to be
147151
suitable as a token value; in these cases it can provide a default token.
148152
Thus, module tokens do not need a variant of the inelegant ``Py_TP_USE_SPEC``.
149153

154+
To help extensions that straddle Python versions, ``PyModuleDef`` addresses
155+
are used as default tokens, and where it's reasonable, they are made
156+
interchangeable with tokens.
157+
158+
159+
Soft-deprecating the existing export hook
160+
-----------------------------------------
161+
162+
The only reason for authors of *existing* extensions to switch to the
163+
API proposed here is that it allows a single module for both free-threaded
164+
and non-free-threaded builds.
165+
It is important that Python *allows* that, but for many existing modules,
166+
it is nowhere near worth losing compatibility with 3.14 and lower versions.
167+
168+
It is much too early to plan deprecation of the old API.
169+
170+
Instead, this PEP proposes to stop adding new features to the ``PyInit_*``
171+
scheme.
172+
After all, the perfect time for extension authors to switch is when they want
173+
to modify module initialization anyway.
174+
150175

151176
Specification
152177
=============
@@ -264,6 +289,10 @@ If specified, using a new ``Py_mod_token`` slot, the module token must:
264289

265290
(Typically, it should point to a static constant.)
266291

292+
When the address of a ``PyModuleDef`` is used as a module's token,
293+
the module should behave as if it was created from that ``PyModuleDef``.
294+
In particular, the module state must have matching layout and semantics.
295+
267296
Modules created using the ``PyModule_FromSlotsAndSpec`` or the
268297
``PyModExport_<NAME>`` export hook can use a new ``Py_mod_token`` slot
269298
to set the token.
@@ -288,8 +317,15 @@ will return 0 on success and -1 on failure:
288317
int PyModule_GetToken(PyObject *, void **token_p)
289318
290319
A new ``PyType_GetModuleByToken`` function will be added, with a signature
291-
like ``PyType_GetModuleByDef`` but a ``void *token`` argument,
292-
and the same behaviour except matching tokens, rather than only defs.
320+
like the existing ``PyType_GetModuleByDef`` but a ``void *token`` argument,
321+
and the same behaviour except matching tokens rather than only defs.
322+
323+
For easier backwards compatibility, the existing ``PyType_GetModuleByDef``
324+
will be changed to work exactly like ``PyType_GetModuleByToken`` -- that is,
325+
it will allow a token (cast to a ``PyModuleDef *`` pointer) as the
326+
*def* argument.
327+
(The ``PyModule_GetDef`` function will not get a similar change, as users may
328+
access members of its result.)
293329

294330

295331
New slots
@@ -333,6 +369,14 @@ via a pointer; the function will return 0 on success and -1 on failure:
333369
int PyModule_GetStateSize(PyObject *, Py_ssize_t *result);
334370
335371
372+
Soft-deprecating the existing export hook
373+
-----------------------------------------
374+
375+
The ``PyInit_*`` export hook will be
376+
:ref:`soft-deprecated <pep387-soft-deprecation>`.
377+
378+
379+
336380
.. _pep793-api-summary:
337381

338382
New API summary
@@ -383,9 +427,14 @@ If an existing module is ported to use the new mechanism, then
383427
We claim that how a module was defined is an implementation detail of that
384428
module, so this should not be considered a breaking change.
385429

386-
Similarly, ``PyType_GetModuleByDef`` will not match modules that are not
387-
defined using a *def*.
388-
The new ``PyType_GetModuleByToken`` function may be used instead.
430+
Similarly, the ``PyType_GetModuleByDef`` function may stop matching modules
431+
whose definition changed. Module authors may avoid this by explicitly
432+
setting a *def* as the *token*.
433+
434+
``PyType_GetModuleByDef`` will now accept a module token as the *def* argument.
435+
We specify a suitable restriction on using ``PyModuleDef`` addresses as tokens,
436+
and non-``PyModuleDef`` pointers were previously invalid input,
437+
so this is not a backwards-compatibility issue.
389438

390439
The ``Py_mod_create`` function may now be called with ``NULL`` for the second
391440
argument.
@@ -412,6 +461,9 @@ Here is a guide to convert an existing module to the new API, including
412461
some tricky edge cases.
413462
It should be moved to a HOWTO in the documentation.
414463

464+
This guide is meant for hand-written modules. For code generators and language
465+
wrappers, the :ref:`pep793-shim` below may be more useful.
466+
415467
#. Scan your code for uses of ``PyModule_GetDef``. This function will
416468
return ``NULL`` for modules that use the new mechanism. Instead:
417469

@@ -425,11 +477,15 @@ It should be moved to a HOWTO in the documentation.
425477
Later in this guide, you'll set the token to *be* the existing
426478
``PyModuleDef`` structure.
427479

428-
#. Scan your code for uses of ``PyType_GetModuleByDef``, and replace them by
429-
``PyType_GetModuleByToken``.
480+
#. Optionally, scan your code for uses of ``PyType_GetModuleByDef``,
481+
and replace them with ``PyType_GetModuleByToken``.
430482
Later in this guide, you'll set the token to *be* the existing
431483
``PyModuleDef`` structure.
432484

485+
(You may skip this step if targetting Python versions that don't expose
486+
``PyType_GetModuleByToken``, since ``PyType_GetModuleByDef`` is
487+
backwards-compatible.)
488+
433489
#. Look at the function identified by ``Py_mod_create``, if any.
434490
Make sure that it does not use its second argument (``PyModuleDef``),
435491
as it will be called with ``NULL``.
@@ -464,18 +520,17 @@ It should be moved to a HOWTO in the documentation.
464520
};
465521
466522
#. If you switched from ``PyModule_GetDef`` to ``PyModule_GetToken``,
467-
and/or from ``PyType_GetModuleByDef`` to ``PyType_GetModuleByToken``,
523+
and/or if you use ``PyType_GetModuleByDef`` or ``PyType_GetModuleByToken``,
468524
add a ``Py_mod_token`` slot pointing to the existing ``PyModuleDef`` struct:
469525

470526
.. code-block:: c
471527
472528
static PyModuleDef_Slot module_slots[] = {
473529
// ... (keep existing slots here)
474-
{Py_mod_token, your_module_def},
530+
{Py_mod_token, &your_module_def},
475531
{0}
476532
};
477533
478-
479534
#. Add a new export hook.
480535

481536
.. code-block:: c
@@ -489,9 +544,55 @@ It should be moved to a HOWTO in the documentation.
489544
}
490545
491546
The new export hook will be used on Python 3.15 and above.
492-
Once your module no longer supports lower versions, delete the ``PyInit_``
493-
function and any unused data.
547+
Once your module no longer supports lower versions:
548+
549+
#. Delete the ``PyInit_`` function.
550+
551+
#. If the existing ``PyModuleDef`` struct is used *only* for ``Py_mod_token``
552+
and/or ``PyType_GetModuleByToken``, you may remove the ``Py_mod_token``
553+
line and replace ``&your_module_def`` with ``module_slots`` everywhere else.
554+
555+
#. Delete any unused data.
556+
The ``PyModuleDef`` struct and the original slots array are likely to be
557+
unused.
558+
559+
560+
.. _pep793-shim:
561+
562+
Backwards compatibility shim
563+
----------------------------
564+
565+
It is possible to write a generic function that implements the “old” export
566+
hook (``PyInit_``) in terms of the API proposed here.
494567

568+
The following implementation can be copied and pasted to a project; only the
569+
names ``PyInit_examplemodule`` (twice) and ``PyModExport_examplemodule`` should
570+
need adjusting.
571+
572+
When added to the :ref:`pep793-example` below and compiled with a
573+
non-free-threaded build of this PEP's reference implementation, the resulting
574+
extension is compatible with non-free-threading 3.9+ builds, in addition to a
575+
free-threading build of the reference implementation.
576+
(The module must be named without a version tag, e.g. ``examplemodule.so``,
577+
and be placed on ``sys.path``.)
578+
579+
Full support for creating such modules will require backports of some new
580+
API, and support in build/install tools. This is out of scope of this PEP.
581+
(In particular, the demo “cheats” by using a subset of Limited API 3.15 that
582+
*happens to work* on 3.9; a proper implementation would use Limited API 3.9
583+
with backport shims for new API like ``Py_mod_name``.)
584+
585+
This implementation places a few additional requirements on the slots array:
586+
587+
- Slots that correspond to ``PyModuleDef`` members must come first.
588+
- A ``Py_mod_name`` slot is required.
589+
- Any ``Py_mod_token`` must be set to ``&module_def_and_token``, defined here.
590+
591+
It also passes ``NULL`` as *spec* to the ``PyModExport`` export hook.
592+
A proper implementation would pass ``None`` instead.
593+
594+
.. literalinclude:: pep-0793/shim.c
595+
:language: c
495596

496597

497598
Security Implications
@@ -507,6 +608,8 @@ In addition to regular reference docs, the :ref:`pep793-porting-notes` should
507608
be added as a new HOWTO.
508609

509610

611+
.. _pep793-example:
612+
510613
Example
511614
=======
512615

peps/pep-0793/examplemodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ PyDoc_STRVAR(examplemodule_doc, "Example extension.");
4949
static PyModuleDef_Slot examplemodule_slots[] = {
5050
{Py_mod_name, "examplemodule"},
5151
{Py_mod_doc, (char*)examplemodule_doc},
52-
{Py_mod_exec, (void*)examplemodule_exec},
5352
{Py_mod_methods, examplemodule_methods},
5453
{Py_mod_state_size, (void*)sizeof(examplemodule_state)},
54+
{Py_mod_exec, (void*)examplemodule_exec},
5555
{0}
5656
};
5757

peps/pep-0793/shim.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include <string.h> // memset
2+
3+
PyMODINIT_FUNC PyInit_examplemodule(void);
4+
5+
static PyModuleDef module_def_and_token;
6+
7+
PyMODINIT_FUNC
8+
PyInit_examplemodule(void)
9+
{
10+
PyModuleDef_Slot *slot = PyModExport_examplemodule(NULL);
11+
12+
if (module_def_and_token.m_name) {
13+
// Take care to only set up the static PyModuleDef once.
14+
// (PyModExport might theoretically return different data each time.)
15+
return PyModuleDef_Init(&module_def_and_token);
16+
}
17+
int copying_slots = 1;
18+
for (/* slot set above */; slot->slot; slot++) {
19+
switch (slot->slot) {
20+
// 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)
38+
case Py_mod_token:
39+
// With PyInit_, the PyModuleDef is used as the token.
40+
if (slot->value != &module_def_and_token) {
41+
PyErr_SetString(PyExc_SystemError,
42+
"Py_mod_token must be set to "
43+
"&module_def_and_token");
44+
goto error;
45+
}
46+
break;
47+
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;
53+
}
54+
copying_slots = 0;
55+
break;
56+
}
57+
}
58+
if (!module_def_and_token.m_name) {
59+
// This function needs m_name as the "is initialized" marker.
60+
PyErr_SetString(PyExc_SystemError, "Py_mod_name slot is required");
61+
goto error;
62+
}
63+
return PyModuleDef_Init(&module_def_and_token);
64+
65+
error:
66+
memset(&module_def_and_token, 0, sizeof(module_def_and_token));
67+
return NULL;
68+
}

0 commit comments

Comments
 (0)