Skip to content

Commit 54cf5eb

Browse files
authored
PEP-793: Updates based on discussion (GH-4651)
- Lose the *spec* argument - Add *const* to arguments we don't change - Show token use in the example - Add rejected idea about changing PyModuleDef
1 parent 5ebca14 commit 54cf5eb

File tree

3 files changed

+118
-23
lines changed

3 files changed

+118
-23
lines changed

peps/pep-0793.rst

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ like this:
184184

185185
.. code-block:: c
186186
187-
PyModuleDef_Slot *PyModExport_<NAME>(PyObject *spec);
187+
PyModuleDef_Slot *PyModExport_<NAME>(void);
188188
189189
where ``<NAME>`` is the name of the module.
190190
For non-ASCII names, it will instead look for ``PyModExportU_<NAME>``,
@@ -194,12 +194,7 @@ with ``<NAME>`` encoded as for existing ``PyInitU_*`` hooks
194194
If not found, the import will continue as in previous Python versions (that is,
195195
by looking up a ``PyInit_*`` or ``PyInitU_*`` function).
196196

197-
If found, Python will call the hook with the appropriate
198-
``importlib.machinery.ModuleSpec`` object as *spec*.
199-
To support duck-typing, extensions should not type-check this object, and
200-
if possible, implement fallbacks for any missing attributes.
201-
(The argument is mainly meant for introspection, testing, or use with
202-
specialized loaders.)
197+
If found, Python will call the hook with no arguments.
203198

204199
On failure, the export hook must return NULL with an exception set.
205200
This will cause the import to fail.
@@ -225,13 +220,17 @@ A new function will be added to create a module from an array of slots:
225220

226221
.. code-block:: c
227222
228-
PyObject *PyModule_FromSlotsAndSpec(PyModuleDef_Slot *slots, PyObject *spec)
223+
PyObject *PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *slots, PyObject *spec)
229224
230225
The *slots* argument must point to an array of ``PyModuleDef_Slot`` structures,
231226
terminated by a slot with ``slot=0`` (typically written as ``{0}`` in C).
232227
There are no required slots, though *slots* must not be ``NULL``.
233228
It follows that minimal input contains only the terminator slot.
234229

230+
.. note::
231+
232+
If :pep:`803` is accepted, the ``Py_mod_abi`` slot will be mandatory.
233+
235234
The *spec* argument is a duck-typed ModuleSpec-like object, meaning that any
236235
attributes defined for ``importlib.machinery.ModuleSpec`` have matching
237236
semantics.
@@ -274,20 +273,33 @@ For modules created from a *def*, calling this is equivalent to
274273
calling ``PyModule_ExecDef(module, PyModule_GetDef(module))``.
275274

276275

276+
.. _pep793-token:
277+
277278
Tokens
278279
------
279280

280281
Module objects will optionally store a “token”: a ``void*`` pointer
281282
similar to ``Py_tp_token`` for types.
282283

284+
.. note::
285+
286+
This is specialized functionality meant replace the
287+
``PyType_GetModuleByDef`` function; users that don't need
288+
``PyType_GetModuleByDef`` will most likely not need tokens either.
289+
290+
This section contains the technical specification;
291+
for an example of intended usage, see ``exampletype_repr`` in the
292+
:ref:`Example section <pep793-example>`.
293+
283294
If specified, using a new ``Py_mod_token`` slot, the module token must:
284295

285296
- outlive the module, so it's not reused for something else while the module
286297
exists; and
287298
- "belong" to the extension module where the module lives, so it will not
288299
clash with other extension modules.
289300

290-
(Typically, it should point to a static constant.)
301+
(Typically, it should be the slots array or ``PyModuleDef`` that a module is
302+
created from, or another static constant for dynamically created modules.)
291303

292304
When the address of a ``PyModuleDef`` is used as a module's token,
293305
the module should behave as if it was created from that ``PyModuleDef``.
@@ -317,7 +329,7 @@ will return 0 on success and -1 on failure:
317329
int PyModule_GetToken(PyObject *, void **token_p)
318330
319331
A new ``PyType_GetModuleByToken`` function will be added, with a signature
320-
like the existing ``PyType_GetModuleByDef`` but a ``void *token`` argument,
332+
like the existing ``PyType_GetModuleByDef`` but a ``const void *token`` argument,
321333
and the same behaviour except matching tokens rather than only defs,
322334
and returning a strong reference.
323335

@@ -388,17 +400,17 @@ Python will load a new module export hook, with two variants:
388400

389401
.. code-block:: c
390402
391-
PyModuleDef_Slot *PyModExport_<NAME>(PyObject *spec);
392-
PyModuleDef_Slot *PyModExportU_<ENCODED_NAME>(PyObject *spec);
403+
PyModuleDef_Slot *PyModExport_<NAME>(void);
404+
PyModuleDef_Slot *PyModExportU_<ENCODED_NAME>(void);
393405
394406
The following functions will be added:
395407

396408
.. code-block:: c
397409
398-
PyObject *PyModule_FromSlotsAndSpec(PyModuleDef_Slot *, PyObject *spec)
410+
PyObject *PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *, PyObject *spec)
399411
int PyModule_Exec(PyObject *)
400412
int PyModule_GetToken(PyObject *, void**)
401-
PyObject *PyType_GetModuleByToken(PyTypeObject *type, void *token)
413+
PyObject *PyType_GetModuleByToken(PyTypeObject *type, const void *token)
402414
int PyModule_GetStateSize(PyObject *, Py_ssize_t *result);
403415
404416
A new macro will be added:
@@ -541,7 +553,7 @@ wrappers, the :ref:`pep793-shim` below may be more useful.
541553
PyMODEXPORT_FUNC PyModExport_examplemodule(PyObject);
542554
543555
PyMODEXPORT_FUNC
544-
PyModExport_examplemodule(PyObject *spec)
556+
PyModExport_examplemodule(void)
545557
{
546558
return module_slots;
547559
}
@@ -591,9 +603,6 @@ This implementation places a few additional requirements on the slots array:
591603
- A ``Py_mod_name`` slot is required.
592604
- Any ``Py_mod_token`` must be set to ``&module_def_and_token``, defined here.
593605

594-
It also passes ``NULL`` as *spec* to the ``PyModExport`` export hook.
595-
A proper implementation would pass ``None`` instead.
596-
597606
.. literalinclude:: pep-0793/shim.c
598607
:language: c
599608

@@ -651,6 +660,33 @@ A function also allows the extension to introspect its environment in a limited
651660
way -- for example, to tailor the returned data to the current Python version.
652661

653662

663+
Changing ``PyModuleDef`` to not be ``PyObject``
664+
-----------------------------------------------
665+
666+
It is possible to change ``PyModuleDef`` to no longer include the ``PyObject``
667+
header, and continue using the current ``PyInit_*`` hook.
668+
There are several issues with this approach:
669+
670+
- The import machinery would need to examine bit-patterns in the objects to
671+
distinguish between different memory layouts:
672+
673+
- the “old” ``PyObject``-based ``PyModuleDef``, returned by current ``abi3``
674+
extensions,
675+
- the new ``PyModuleDef``,
676+
- ``PyObject``-based module objects, for single-phase initialization.
677+
678+
This is fragile, and places constraints on future changes to ``PyObject``:
679+
the memory layouts need to stay *distinguishable* until both single-phase
680+
initialization and the current Stable ABI are no longer supported.
681+
682+
683+
- ``PyModuleDef_Init`` is documented to “Ensure a module definition is a
684+
properly initialized Python object that correctly reports its type and
685+
a reference count.”
686+
This would need to change without warning, breaking any user code that treats
687+
``PyModuleDef``\ s as Python objects.
688+
689+
654690
Possible Future Directions
655691
==========================
656692

peps/pep-0793/examplemodule.c

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/*
2-
Example module with C-level module-global state, and a simple function to
3-
update and query it.
2+
Example module with C-level module-global state, and
3+
4+
- a simple function that updates and queries the state
5+
- a class wihose repr() queries the same module state (as an example of
6+
PyType_GetModuleByToken)
7+
48
Once compiled and renamed to not include a version tag (for example
59
examplemodule.so on Linux), this will run succesfully on both regular
610
and free-threaded builds.
@@ -13,6 +17,13 @@ print(examplemodule.increment_value()) # 1
1317
print(examplemodule.increment_value()) # 2
1418
print(examplemodule.increment_value()) # 3
1519
20+
21+
class Subclass(examplemodule.ExampleType):
22+
pass
23+
24+
instance = Subclass()
25+
print(instance) # <Subclass object; module value = 3>
26+
1627
*/
1728

1829
// Avoid CPython-version-specific ABI (inline functions & macros):
@@ -24,6 +35,10 @@ typedef struct {
2435
int value;
2536
} examplemodule_state;
2637

38+
static PyModuleDef_Slot examplemodule_slots[];
39+
40+
// increment_value function
41+
2742
static PyObject *
2843
increment_value(PyObject *module, PyObject *_ignored)
2944
{
@@ -37,10 +52,54 @@ static PyMethodDef examplemodule_methods[] = {
3752
{NULL}
3853
};
3954

55+
// ExampleType
56+
57+
static PyObject *
58+
exampletype_repr(PyObject *self)
59+
{
60+
/* To get module state, we cannot use PyModule_GetState(Py_TYPE(self)),
61+
* since Py_TYPE(self) might be a subclass defined in an unrelated module.
62+
* So, use PyType_GetModuleByToken.
63+
*/
64+
PyObject *module = PyType_GetModuleByToken(
65+
Py_TYPE(self), examplemodule_slots);
66+
if (!module) {
67+
return NULL;
68+
}
69+
examplemodule_state *state = PyModule_GetState(module);
70+
Py_DECREF(module);
71+
if (!state) {
72+
return NULL;
73+
}
74+
return PyUnicode_FromFormat("<%T object; module value = %d>",
75+
self, state->value);
76+
}
77+
78+
static PyType_Spec exampletype_spec = {
79+
.name = "examplemodule.ExampleType",
80+
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
81+
.slots = (PyType_Slot[]) {
82+
{Py_tp_repr, exampletype_repr},
83+
{0},
84+
},
85+
};
86+
87+
// Module
88+
4089
static int
4190
examplemodule_exec(PyObject *module) {
4291
examplemodule_state *state = PyModule_GetState(module);
4392
state->value = -1;
93+
PyTypeObject *type = (PyTypeObject*)PyType_FromModuleAndSpec(
94+
module, &exampletype_spec, NULL);
95+
if (!type) {
96+
return -1;
97+
}
98+
if (PyModule_AddType(module, type) < 0) {
99+
Py_DECREF(type);
100+
return -1;
101+
}
102+
Py_DECREF(type);
44103
return 0;
45104
}
46105

@@ -56,10 +115,10 @@ static PyModuleDef_Slot examplemodule_slots[] = {
56115
};
57116

58117
// Avoid "implicit declaration of function" warning:
59-
PyMODEXPORT_FUNC PyModExport_examplemodule(PyObject *);
118+
PyMODEXPORT_FUNC PyModExport_examplemodule(void);
60119

61120
PyMODEXPORT_FUNC
62-
PyModExport_examplemodule(PyObject *spec)
121+
PyModExport_examplemodule(void)
63122
{
64123
return examplemodule_slots;
65124
}

peps/pep-0793/shim.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ static PyModuleDef module_def_and_token;
77
PyMODINIT_FUNC
88
PyInit_examplemodule(void)
99
{
10-
PyModuleDef_Slot *slot = PyModExport_examplemodule(NULL);
10+
PyModuleDef_Slot *slot = PyModExport_examplemodule();
1111

1212
if (module_def_and_token.m_name) {
1313
// Take care to only set up the static PyModuleDef once.

0 commit comments

Comments
 (0)