-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
PEP 793: PyModExport: A new entry point for C extension modules #4435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
General points:
- I think the PEP would benefit from more examples. Perhaps a full example of a module only using
PyModExport_*
, an example on porting, and an example on using both export hooks at once, e.g. for a module supporting Python 3.11--3.15? - The text is very heavy on parenthetical asides. I've suggested removing some, but in general I think where full sentences are in brackets, the text would be stronger by properly incorporating the thought and removing the brackets.
- Please replace typographical quotation marks with
"
/'
, Sphinx/Docutils insert them automatically. - Naming (sorry!): Currently,
PyMod*
is only used byPyModInitFunction
. WouldPyModuleExport
be considered?
A
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General: I think the PEP would benefit from a full worked example, perhaps in How to Teach This. It's been noted that one of the problems with multi-phase is a lack of guidance on how to do a migration. A simple example of a 'spam' module would help readers understand what the end state is meant to look like, and could be repurposed for the planned documentation.
|
||
On failure, the export hook must return NULL with an exception set. | ||
This will cause the import to fail. | ||
(Python will not fall back to ``PyInit_*`` on error.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps explicitly mention that defining both PyModExport_<NAME>
and PyInit_<NAME>
is allowed, and indeed expected for modules bridging Python versions?
|
||
If found, Python will call the hook with the appropriate | ||
``importlib.machinery.ModuleSpec`` object as *spec*. | ||
To support duck-typing, extensions should not type-check this object, and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To support duck-typing, extensions should not type-check this object, and | |
To support duck-typing, extension modules should not type-check this object, and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“Extension” could mean the extension author, extension code, the shared library, the module object -- take your pick.
In this instance, any of that is appropriate, so I don't want to use a more specific term.
(We expect functions to export a static constant, or one of several constants | ||
chosen depending on, for example, ``Py_Version``. Dynamic behaviour should | ||
generally happen in the ``Py_mod_create`` and ``Py_mod_exec`` functions.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(We expect functions to export a static constant, or one of several constants | |
chosen depending on, for example, ``Py_Version``. Dynamic behaviour should | |
generally happen in the ``Py_mod_create`` and ``Py_mod_exec`` functions.) | |
We expect functions to export a static constant, or one of several constants | |
chosen depending on, for example, ``Py_Version``. Dynamic behaviour should | |
generally happen in the ``Py_mod_create`` and ``Py_mod_exec`` functions. |
|
||
.. code-block:: c | ||
|
||
PyObject *PyModule_FromSlotsAndSpec(PyModuleDef_Slot *, PyObject *spec) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The export hook is part of the API, but the functions are defined in extension modules. Worth including?
PyObject *PyModule_FromSlotsAndSpec(PyModuleDef_Slot *, PyObject *spec) | |
PyModuleDef_Slot *PyModExport_<NAME>(PyObject *spec); | |
PyObject *PyModule_FromSlotsAndSpec(PyModuleDef_Slot *, PyObject *spec) |
Backwards Compatibility | ||
======================= |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there should be greater emphasis on compatibility code for extension modules supporting older versions, but wanting to use the better/newer mechanism for 3.15+. Either in Backwards Compatibility or How to Teach This.
For instance, the multiple Py_mod_exec
is solvable with a new function that calls all existing functions sequentially. Any suggested mechanisms #if ...
for defining both a PyInit
and PyModExport
export hook? Anything that could go into pythoncapi-compat to make transition easier?
A workaround is to check ``Py_Version`` in the export function, | ||
and return a slot array suitable for the current interpreter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or, use #ifdef Py_<slot>
or #if PY_VERSION_HEX >= ...
.
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: Hood Chatham <roberthoodchatham@gmail.com> Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the reviews! I went through some of the comments, will get to the rest later.
kid is waking up, I left some replies terse; please imagine a most friendly & respectful tone :)
I think the PEP would benefit from more examples. Perhaps a full example of a module only using PyModExport_*, an example on porting, and an example on using both export hooks at once, e.g. for a module supporting Python 3.11--3.15?
Agree, but these should end up in docs and I don't want people reading them here after acceptance. I'll add them to the reference implementation and point the PEP there.
The text is very heavy on parenthetical asides. I've suggested removing some, but in general I think where full sentences are in brackets, the text would be stronger by properly incorporating the thought and removing the brackets.
I use them to separate the hard spec from “mere“ implications/explanations. They are very helpful for me, and I like to think they're not too distracting for the reader.
Please replace typographical quotation marks with "/', Sphinx/Docutils insert them automatically.
I don't see a reason to do that. Any future editor is free to use "
instead, it's not like Python identifiers which you need to repeat exactly somewhere else.
Naming (sorry!): Currently, PyMod* is only used by PyModInitFunction. Would PyModuleExport be considered?
That's a point for the discussion :)
(Yes, I considered it, but my conclusion is not strong enough for a Rejected Ideas entry and the question is not important enough for Open Questions. I'm literally waiting for the discussion thread here.)
The slot will be optional, but extensions will be strongly encouraged to | ||
include it for the benefit of future APIs, external tooling, | ||
and debugging/introspection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or maybe
The slot will be optional, but extensions will be strongly encouraged to | |
include it for the benefit of future APIs, external tooling, | |
and debugging/introspection. | |
The slot will be optional, but extension authors are strongly encouraged to | |
include it for the benefit of future APIs, external tooling, debugging, | |
and introspection. |
All new slots -- these and ``Py_tp_token`` discussed above -- may not be | ||
repeated in the slots array, and may not be used in a | ||
``PyModuleDef.m_slots`` array. | ||
They may not have a ``NULL`` value (instead, the slot can be omitted entirely). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to specifically point out that it's both the ones from the previous paragraph and Py_tp_token
. This behaviour is “good“ for both (for reasons either obvious or mentioned in the Py_tp_token
section), but the reviewer should think about them separately.
For modules created without a ``PyModuleDef``, the ``Py_mod_create`` function | ||
will be called with ``NULL`` for the second argument. | ||
(In the future, if we find a use case for passing the input slots array, a new | ||
slot with an updated signature can be added.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For modules created without a ``PyModuleDef``, the ``Py_mod_create`` function | |
will be called with ``NULL`` for the second argument. | |
(In the future, if we find a use case for passing the input slots array, a new | |
slot with an updated signature can be added.) | |
For modules created without a ``PyModuleDef``, the ``Py_mod_create`` function | |
will be called with ``NULL`` for the second argument (*def*). | |
(In the future, if we find a use case for passing the input slots array, a new | |
slot with an updated signature can be added.) |
To simplify the implementation, the slots arrays for both | ||
``PyModule_FromSlotsAndSpec`` and the new export hook will only allow up to one | ||
``Py_mod_exec`` slot. | ||
(Arrays in ``PyModuleDef.m_slots`` may have more; this will not change.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that is the main argument. I don't want to maintain the code for that :)
I can do what I did for other minor inconveniences:
To simplify the implementation, the slots arrays for both | |
``PyModule_FromSlotsAndSpec`` and the new export hook will only allow up to one | |
``Py_mod_exec`` slot. | |
(Arrays in ``PyModuleDef.m_slots`` may have more; this will not change.) | |
To simplify the implementation, the slots arrays for both | |
``PyModule_FromSlotsAndSpec`` and the new export hook will only allow up to one | |
``Py_mod_exec`` slot. | |
(Arrays in ``PyModuleDef.m_slots`` may have more; this will not change.) | |
This limitation might be lifted in the future. |
FWIW, I'd like to lift the other limitations in this PEP -- in dedicated PRs with loads of tests. This limitation isn't worth it IMO.
|
||
If found, Python will call the hook with the appropriate | ||
``importlib.machinery.ModuleSpec`` object as *spec*. | ||
To support duck-typing, extensions should not type-check this object, and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“Extension” could mean the extension author, extension code, the shared library, the module object -- take your pick.
In this instance, any of that is appropriate, so I don't want to use a more specific term.
|
||
.. code-block:: c | ||
|
||
PyModuleDef_Slot *PyModExport_<NAME>(PyObject *spec); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion! I'll leave it for the API summary.
Specification | ||
============= | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proper title for this subsection would be PyModExport: A new entry point for C extension modules
. But it's already under that heading :)
This is the main thing; the rest is supporting API.
|
||
New API summary | ||
--------------- | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python will load a new module export hook, with two variants: | |
.. code-block:: c | |
PyModuleDef_Slot *PyModExport_<NAME>(PyObject *spec); | |
PyModuleDef_Slot *PyModExportU_<ENCODED_NAME>(PyObject *spec); | |
Basic requirements (all PEP Types)
pep-NNNN.rst
), PR title (PEP 123: <Title of PEP>
) andPEP
headerAuthor
orSponsor
, and formally confirmed their approvalAuthor
,Status
(Draft
),Type
andCreated
headers filled out correctlyPEP-Delegate
,Topic
,Requires
andReplaces
headers completed if appropriate.github/CODEOWNERS
for the PEPStandards Track requirements
Python-Version
set to valid (pre-beta) future Python version, if relevantDiscussions-To
andPost-History
📚 Documentation preview 📚: https://pep-previews--4435.org.readthedocs.build/pep-0793/