<br><div class="gmail_extra"><br><br><div class="gmail_quote">On Thu, Nov 8, 2012 at 7:47 AM, Stefan Behnel <span dir="ltr"><<a href="mailto:stefan_ml@behnel.de" target="_blank">stefan_ml@behnel.de</a>></span> wrote:<br>

<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi,<br>
<br>
I suspect that this will be put into a proper PEP at some point, but I'd<br>
like to bring this up for discussion first. This came out of issues 13429<br>
and 16392.<br>
<br>
<a href="http://bugs.python.org/issue13429" target="_blank">http://bugs.python.org/issue13429</a><br>
<br>
<a href="http://bugs.python.org/issue16392" target="_blank">http://bugs.python.org/issue16392</a><br>
<br>
Stefan<br>
<br>
<br>
The problem<br>
===========<br>
<br>
Python modules and extension modules are not being set up in the same way.<br>
For Python modules, the module is created and set up first, then the module<br>
code is being executed. For extensions, i.e. shared libraries, the module<br>
init function is executed straight away and does both the creation and<br>
initialisation. This means that it knows neither the __file__ it is being<br>
loaded from nor its package (i.e. its FQMN). This hinders relative imports<br>
and resource loading. In Py3, it's also not being added to sys.modules,<br>
which means that a (potentially transitive) re-import of the module will<br>
really try to reimport it and thus run into an infinite loop when it<br>
executes the module init function again. And without the FQMN, it's not<br>
trivial to correctly add the module to sys.modules either.<br>
<br>
We specifically run into this for Cython generated modules, for which it's<br>
not uncommon that the module init code has the same level of complexity as<br>
that of any 'regular' Python module. Also, the lack of a FQMN and correct<br>
file path hinders the compilation of __init__.py modules, i.e. packages,<br>
especially when relative imports are being used at module init time.<br></blockquote><div><br></div><div>Or to put it another way, importlib doesn't give you a nice class to inherit from which will handle all of the little details of creating a blank module (or fetching from sys.modules if you are reloading), setting __file__, __cached__, __package__, __name__, __loader__, and (optionally) __path__ for you, and then cleaning up if something goes wrong. It's a pain to do all of this yourself and to get all the details right (i.e. there's a reason that @importlib.util.module_for_loader exists).</div>

<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
The proposal<br>
============<br>
<br>
I propose to split the extension module initialisation into two steps in<br>
Python 3.4, in a backwards compatible way.<br>
<br>
Step 1: The current module init function can be reduced to just creating<br>
the module instance and returning it (and potentially doing some simple C<br>
level setup). Optionally, after creating the module (and this is the new<br>
part), the module init code can register a C callback function that will be<br>
called after setting up the module.<br></blockquote><div><br></div><div>Why even bother with the module creation? Why can't Python do that as well and then call the callback?</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">


<br>
Step 2: The shared library importer receives the module instance from the<br>
module init function, adds __file__, __path__, __package__ and friends to<br>
the module dict, and then checks for the callback. If non-NULL, it calls it<br>
to continue the module initialisation by user code. </blockquote><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
The callback<br>
============<br>
<br>
The callback is defined as follows::<br>
<br>
    int (*PyModule_init_callback)(PyObject* the_module,<br>
                                  PyModuleInitContext* context)<br>
<br>
"PyModuleInitContext" is a struct that is meant mostly for making the<br>
callback more future proof by allowing additional parameters to be passed<br>
in. For now, I can see a use case for the following fields::<br>
<br>
    struct PyModuleInitContext {<br>
        char* module_name;<br>
        char* qualified_module_name;<br>
    }<br>
<br>
Both names are encoded in UTF-8. As for the file path, I consider it best<br>
to retrieve it from the module's __file__ attribute as a Python string<br>
object to reduce filename encoding problems.<br>
<br>
Note that this struct argument is not strictly required, but given that<br>
this proposal would have been much simpler if the module init function had<br>
accepted such an argument in the first place, I consider it a good idea not<br>
to let this chance pass by again.<br>
<br>
The registration of the callback uses a new C-API function:<br>
<br>
    int PyModule_SetInitFunction(PyObject* module,<br>
                                 PyModule_init_callback callback)<br>
<br>
The function name uses "Set" instead of "Register" to make it clear that<br>
there is only one such function per module.<br>
<br>
An alternative would be a new module creation function "PyModule_Create3()"<br>
that takes the callback as third argument, in addition to what<br>
"PyModule_Create2()" accepts. This would require users to explicitly pass<br>
in the (second) version argument, which might be considered only a minor issue.<br>
<br>
Implementation<br>
==============<br>
<br>
The implementation requires local changes to the extension module importer<br>
and a new C-API function. In order to store the callback, it should use a<br>
new field in the module object struct.<br>
<br>
Open questions<br>
==============<br>
<br>
It is not clear how extensions should be handled that register more than<br>
one module in their module init function, e.g. compiled packages. One<br>
possibility would be to leave the setup to the user, who would have to know<br>
all FQMNs anyway in this case, although not the import file path.<br>
Alternatively, the import machinery could use a stack to remember for which<br>
modules a callback was registered during the last init function call, set<br>
up all of them and then call their callbacks. It's not clear if this meets<br>
the intention of the user.<br>
<br>
Alternatives<br>
============<br>
<br>
1) It would be possible to make extension modules optionally export another<br>
symbol, e.g. "PyInit2_modulename", that the shared library loader would<br>
call in addition to the required function "PyInit_modulename". This would<br>
remove the need for a new API that registers the above callback. The<br>
drawback is that it also makes it easier to write broken code because a<br>
Python version or implementation that does not support this second symbol<br>
would simply not call it, without error. The new C-API function would let<br>
the build fail instead if it is not supported.<br></blockquote><div><br></div><div>An alternative to the alternative is that if the PyInit2 function exists it's called instead of the the PyInit function, and then the PyInit function is nothing more than a single line function call (or whatever the absolute bare minimum is) into some helper that calls the PyInit2 call properly for backwards ABI compatibility (i.e. passes in whatever details are lost by the indirection in function call). That provides an eventual upgrade path of dropping PyInit and moving over to PyInit2.</div>

<div><br></div><div>-Brett</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
2) The callback could be made available as a Python function in the module<br>
dict, thus also removing the need for an explicit registration API.<br>
However, this approach would add overhead to both sides, the importer code<br>
and the user provided module init code, as it would require additional<br>
dictionary handling and the implementation of a one-time Python function in<br>
user code. It would also suffer from the problem that missing support in<br>
the runtime would pass silently.<br>
<br>
3) The callback could be registered statically in the PyModuleDef struct by<br>
adding a new field. This is not trivial to do in a backwards compatible way<br>
because the struct would grow longer without explicit initialisation by<br>
existing user code. Extending PyModuleDef_HEAD_INIT might be possible but<br>
would still break at least binary compatibility.<br>
<br>
4) Pass a new context argument into the module init function that contains<br>
all information necessary to properly and completely set up the module at<br>
creation time. This would provide a much simpler and cleaner solution than<br>
the proposed solution. However, it will not be possible before Python 4 as<br>
it breaks backwards compatibility with all existing extension modules at<br>
both the source and binary level.<br>
<br>
_______________________________________________<br>
Python-Dev mailing list<br>
<a href="mailto:Python-Dev@python.org">Python-Dev@python.org</a><br>
<a href="http://mail.python.org/mailman/listinfo/python-dev" target="_blank">http://mail.python.org/mailman/listinfo/python-dev</a><br>
Unsubscribe: <a href="http://mail.python.org/mailman/options/python-dev/brett%40python.org" target="_blank">http://mail.python.org/mailman/options/python-dev/brett%40python.org</a><br>
</blockquote></div><br></div>