Skip to content

Commit 8c0258a

Browse files
Add an explanation about single-phase init variants.
1 parent 6c9bd47 commit 8c0258a

File tree

1 file changed

+65
-0
lines changed

1 file changed

+65
-0
lines changed

Python/import.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,71 @@ PyImport_GetMagicTag(void)
428428
}
429429

430430

431+
/*
432+
We support a number of kinds of single-phase init builtin/extension modules:
433+
434+
* "basic"
435+
* no module state (PyModuleDef.m_size == -1)
436+
* does not support repeated init (we use PyModuleDef.m_base.m_copy)
437+
* may have process-global state
438+
* the module's def is cached in _PyRuntime.imports.extensions,
439+
by (name, filename)
440+
* "reinit"
441+
* no module state (PyModuleDef.m_size == 0)
442+
* supports repeated init (m_copy is never used)
443+
* should not have any process-global state
444+
* its def is never cached in _PyRuntime.imports.extensions
445+
(except, currently, under the main interpreter, for some reason)
446+
* "with state" (almost the same as reinit)
447+
* has module state (PyModuleDef.m_size > 0)
448+
* supports repeated init (m_copy is never used)
449+
* should not have any process-global state
450+
* its def is never cached in _PyRuntime.imports.extensions
451+
(except, currently, under the main interpreter, for some reason)
452+
453+
There are also variants within those classes:
454+
455+
* two or more modules share a PyModuleDef
456+
* a module's init func uses another module's PyModuleDef
457+
* a module's init func calls another's module's init func
458+
* a module's init "func" is actually a variable statically initialized
459+
to another module's init func
460+
* two or modules share "methods"
461+
* a module's init func copies another module's PyModuleDef
462+
(with a different name)
463+
* (basic-only) two or modules share process-global state
464+
465+
In the first case, where modules share a PyModuleDef, the following
466+
notable weirdness happens:
467+
468+
* the module's __name__ matches the def, not the requested name
469+
* the last module (with the same def) to be imported for the first time wins
470+
* returned by PyState_Find_Module() (via interp->modules_by_index)
471+
* (non-basic-only) its init func is used when re-loading any of them
472+
(via the def's m_init)
473+
* (basic-only) the copy of its __dict__ is used when re-loading any of them
474+
(via the def's m_copy)
475+
476+
However, the following happens as expected:
477+
478+
* a new module object (with its own __dict__) is created for each request
479+
* the module's __spec__ has the requested name
480+
* the loaded module is cached in sys.modules under the requested name
481+
* the m_index field of the shared def is not changed,
482+
so at least PyState_FindModule() will always look in the same place
483+
484+
For "basic" modules there are other quirks:
485+
486+
* (whether sharing a def or not) when loaded the first time,
487+
m_copy is set before _init_module_attrs() is called
488+
in importlib._bootstrap.module_from_spec(),
489+
so when the module is re-loaded, the previous value
490+
for __wpec__ (and others) is reset, possibly unexpectedly.
491+
492+
Generally, when multiple interpreters are involved, some of the above
493+
gets even messier.
494+
*/
495+
431496
/* Magic for extension modules (built-in as well as dynamically
432497
loaded). To prevent initializing an extension module more than
433498
once, we keep a static dictionary 'extensions' keyed by the tuple

0 commit comments

Comments
 (0)