Repository navigation
Expose a C-API function to allow custom importers to create a module using an init function #116146
Description
Activity
- addedtype-featureA feature request or enhancementA feature request or enhancement
on Feb 29, 2024 I don't know if I'm speaking out of turn here, but this API looks fine to me, except that it appears there is no (longer a) function named
_PyImport_InitFunc_TrampolineCall-- the code in main just callsinitfunc, presumably because our WASI support has improved. It certainly looks better than adding (again) a public API for_PyImport_SwapPackageContext.Also, there may be constituencies that might have a problem with this API, since it seems the modules thus created don't participate in the two-phase init protocol (PEP 489), and hence won't work smoothly in an environment using multiple sub-interpreters. For this I'll call out @ericsnowcurrently and @encukou.
Reacted by Itamar Oren and Petr ViktorinThe multi-phase init protocol needs a ModuleSpec object. In your use case, do you have that handy? (Single-phase modules happen to only need spec's
nameattribute, so that's passed viaPackageContext; the new API uses an object with extra info.)
IfPyImport_ImportByInitFunccan take a spec, then the new function should check ifmodis aPyModuleDef_Typerather than a module, and if so it should callPyModule_FromDefAndSpecandPyModule_ExecDef.For a prototype I'm working on, related to what (e.g.)
pybind11does, this would come in very handy: being able to reuse most of the existing extension loading support in the interpreter, without being tied to individual shared libraries andPyInit_modnamesymbols, but passing such function in, would be great. Multi-phase init support would be a big plus as well.One question though: would it make sense to extend the API to something along the lines of
PyObject* PyImport_ImportByInitFunc(const char* fullname, PyObject* (*initfunc)(void *arg), void *arg)
so a loader calling this function can pass some information to this init function? I guess in C++ one could use a lambda to close over some data, then pass that in as
initfunc(not sure, not a C++ expert), but in plain C that's quite a bit more difficult. This would allow forinitfuncto be more dynamic and be (re)used to load multiple modules.- added 3 commits that reference this issue
on Mar 5, 2025 I drafted gh-139196 as the proposed C-API,
PyImport_CreateBuiltinFromSpecAndInitfunc, that would enable my use-case.
Before I work on tests and docs and news etc, I'd like to get more feedback about the API itself.
For development purposes, I did test it using this demo repo: https://github.com/itamaro/python-static-importer-demo
I also patched our internal 3.14 using this patch and it seems to work.@encukou wdyt? Since my use-case indeed has a
specalready, and what is semantically wants to do is "create a builtin, just like inittab allows, but without using inittab", I thought it would make most sense to make sure it uses the same code path that inittab would use, i.e., go throughcreate_builtin.@NicolasT sorry I'm not sure I follow what you're suggesting. We don't change the signature of the initfunc (that would break the Stable ABI), so I don't see how we could add arbitrary args.
While I don't fully understand your scenario, perhaps it could be supported without additional changes? The new API is not providing new Finder/Loader, it only exposes a C-API that can be leveraged by custom Finder/Loader (see the demo repo I linked above for a fully working example), where you could do whatever you want.- addedinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
on Sep 22, 2025 - added a commit that references this issue
on Sep 23, 2025 It's now in; thank you for the feature!
Reacted by Itamar Oren- added 2 commits that reference this issue
on Jul 23, 2026
Feature or enhancement
Proposal:
See full details and discussion in this Discourse thread.
To summarize:
Custom importers may want to create a module object by directly calling the init function (e.g. because the module is a statically linked extension module, and inittab is not desired (i.e. due to performance or other considerations)).
This is currently not possible in all scenarios (i.e. when importing pybind11 submodules), because custom importers can't access the "package context" (
_PyRuntime.imports.pkgcontext), or use the internal CPython function to swap package context (_PyImport_SwapPackageContext).From the Discourse discussion it seems the preferred solution is to introduce a C-API function,
PyImport_ImportByInitFunc(const char *fullname, PyObject* (*initfunc)(void))(exact signature up for bikeshedding, function "stability" should be discussed further).Proposed implementation:
Has this already been discussed elsewhere?
I have already discussed this feature proposal on Discourse
Links to previous discussion of this feature:
https://discuss.python.org/t/c-api-for-initializing-statically-linked-extension-modules/43396/32
Linked PRs
_Py_ext_module_loader_infoinitialization #157758