Skip to main content

Objects/moduleobject.c (part 2)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/moduleobject.c

This annotation covers multi-phase module initialization (PEP 489, Python 3.5+). See objects_moduleobject_detail for single-phase PyModule_Create, PyModule_AddObject, and __dict__ layout.

Map

LinesSymbolRole
1-80PyModuleDef_InitRegister a PyModuleDef with the import machinery
81-200PyModule_FromDefAndSpecCreate a module object from def + ModuleSpec
201-350PyModule_ExecDefRun Py_mod_exec slots in order
351-500_PyModuleDef_IS_INITIALIZEDCheck if multi-phase init has run
501-700__spec__Set/get module.__spec__ and module.__loader__

Reading

Multi-phase init overview

// CPython: Objects/moduleobject.c:28 multi-phase comment
/* Multi-phase init (PEP 489):
Phase 1 — PyModuleDef_Init: validate def, register with the runtime.
Phase 2 — PyModule_FromDefAndSpec: allocate the module object,
run Py_mod_create slot if present (or use PyModule_New).
Phase 3 — PyModule_ExecDef: run Py_mod_exec slots to populate __dict__.
This split allows sub-interpreters to share the module definition
while each gets its own module object. */

PyModuleDef_Init

// CPython: Objects/moduleobject.c:62 PyModuleDef_Init
PyObject *
PyModuleDef_Init(struct PyModuleDef *def)
{
if (def->m_base.m_index == 0) {
/* First call: assign a unique module index */
max_module_number++;
def->m_base.m_index = max_module_number;
}
return (PyObject *)def;
}

The m_index is used to cache per-interpreter module state. It is assigned once per process, not per interpreter.

PyModule_FromDefAndSpec

// CPython: Objects/moduleobject.c:120 PyModule_FromDefAndSpec
PyObject *
PyModule_FromDefAndSpec(struct PyModuleDef *def, PyObject *spec)
{
PyObject *mod;
/* Run Py_mod_create slot if the module wants a custom type */
if (def->m_slots) {
for (PyModuleDef_Slot *cur_slot = def->m_slots;
cur_slot->slot; cur_slot++) {
if (cur_slot->slot == Py_mod_create) {
mod = ((PyObject *(*)(PyObject *, PyModuleSpec *))
cur_slot->value)(NULL, (PyModuleSpec *)spec);
goto have_mod;
}
}
}
mod = PyModule_NewObject(PyObject_GetAttrString(spec, "name"));
have_mod:
/* Attach __spec__ to the module */
PyObject_SetAttrString(mod, "__spec__", spec);
return mod;
}

PyModule_ExecDef

// CPython: Objects/moduleobject.c:230 PyModule_ExecDef
int
PyModule_ExecDef(PyObject *module, struct PyModuleDef *def)
{
if (def->m_slots == NULL) return 0;
for (PyModuleDef_Slot *cur_slot = def->m_slots;
cur_slot->slot; cur_slot++) {
if (cur_slot->slot == Py_mod_exec) {
int ret = ((int (*)(PyObject *))cur_slot->value)(module);
if (ret != 0) {
if (!PyErr_Occurred())
PyErr_Format(PyExc_SystemError,
"execution of module %s failed without setting an exception",
PyModule_GetName(module));
return -1;
}
}
}
return 0;
}

Multiple Py_mod_exec slots can be registered; they are called in order. Each can add attributes to the module's __dict__.

gopy notes

gopy uses single-phase init for built-in modules (objects/module.go). Multi-phase init is supported for C extensions loaded via dlopen; PyModuleDef_Init and PyModule_ExecDef are in objects/module_multiphase.go. __spec__ is set by the import machinery in vm/eval_import.go.