Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Doc/c-api/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,20 @@ Importing Modules
(Custom importers should do this in their
:py:meth:`~importlib.abc.Loader.exec_module` method.)

If *initfunc* uses legacy single-phase initialization (that is, it
creates the module with :c:func:`PyModule_Create`), the module is fully
initialized by *initfunc* itself, and it is also added to
:data:`sys.modules` under the spec's name, as is done for modules
registered with :c:func:`PyImport_AppendInittab`.
Calling :c:func:`PyModule_Exec` on such a module is still safe.

The spec's name identifies the module for the purposes of the import
system, in the same way as for built-in modules.
If a single-phase init module was previously created under the same name
(by this function, or in another interpreter), the existing module
definition is reused and *initfunc* is not called, so a later call with
the same name but a different *initfunc* has no effect.

On error, return NULL with an exception set.

.. versionadded:: 3.15
20 changes: 14 additions & 6 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,20 @@ def test_create_module_from_initfunc(self):
out, err = self.run_embedded_interpreter("test_create_module_from_initfunc")
self.assertEqual(self._nogil_filtered_err(err, "embedded_ext"), "")
self.assertEqual(out,
"<module 'my_test_extension' (static-extension)>\n"
"my_test_extension.executed='yes'\n"
"my_test_extension.exec_slot_ran='yes'\n"
"<module 'embedded_ext' (static-extension)>\n"
"embedded_ext.executed='yes'\n"
)
# multi-phase init: not in sys.modules until importlib adds it
"created my_test_extension: in sys.modules=False\n"
"<module 'my_test_extension' (static-extension)>\n"
"my_test_extension.executed='yes'\n"
"my_test_extension.exec_slot_ran='yes'\n"
# single-phase init: added to sys.modules by the init function
"created embedded_ext: in sys.modules=True\n"
"<module 'embedded_ext' (static-extension)>\n"
"embedded_ext.executed='yes'\n"
# same name, different initfuncs: the cached module is
# returned and the second initfunc is never called
"a.which='A' b.which='A' a is b=True\n"
"create_static_module.initfunc_calls()=(1, 0)\n"
)

def test_inittab_submodule_multiphase(self):
out, err = self.run_embedded_interpreter("test_inittab_submodule_multiphase")
Expand Down
69 changes: 68 additions & 1 deletion Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -2410,6 +2410,58 @@ static int test_repeated_init_and_inittab(void)
return 0;
}

// Two different single-phase init functions for the same module name.
static int cmfi_initfunc_a_calls = 0;
static int cmfi_initfunc_b_calls = 0;

static PyModuleDef cmfi_same_name_a_def = {
PyModuleDef_HEAD_INIT,
.m_name = "same_name",
.m_size = -1,
};

static PyModuleDef cmfi_same_name_b_def = {
PyModuleDef_HEAD_INIT,
.m_name = "same_name",
.m_size = -1,
};

static PyObject*
PyInit_cmfi_same_name_a(void)
{
cmfi_initfunc_a_calls++;
PyObject *mod = PyModule_Create(&cmfi_same_name_a_def);
if (mod == NULL || PyModule_AddStringConstant(mod, "which", "A") < 0) {
Py_XDECREF(mod);
return NULL;
}
return mod;
}

static PyObject*
PyInit_cmfi_same_name_b(void)
{
cmfi_initfunc_b_calls++;
PyObject *mod = PyModule_Create(&cmfi_same_name_b_def);
if (mod == NULL || PyModule_AddStringConstant(mod, "which", "B") < 0) {
Py_XDECREF(mod);
return NULL;
}
return mod;
}

static PyObject*
create_same_name_b(PyObject* self, PyObject* spec)
{
return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_same_name_b);
}

static PyObject*
initfunc_calls(PyObject* self, PyObject* Py_UNUSED(args))
{
return Py_BuildValue("(ii)", cmfi_initfunc_a_calls, cmfi_initfunc_b_calls);
}

static PyObject*
create_module(PyObject* self, PyObject* spec)
{
Expand All @@ -2425,6 +2477,10 @@ create_module(PyObject* self, PyObject* spec)
Py_DECREF(name);
return PyImport_CreateModuleFromInitfunc(spec, PyInit_embedded_ext);
}
if (PyUnicode_EqualToUTF8(name, "same_name")) {
Py_DECREF(name);
return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_same_name_a);
}
PyErr_Format(PyExc_LookupError, "static module %R not found", name);
Py_DECREF(name);
return NULL;
Expand All @@ -2441,6 +2497,8 @@ exec_module(PyObject* self, PyObject* mod)

static PyMethodDef create_static_module_methods[] = {
{"create_module", create_module, METH_O, NULL},
{"create_same_name_b", create_same_name_b, METH_O, NULL},
{"initfunc_calls", initfunc_calls, METH_NOARGS, NULL},
{"exec_module", exec_module, METH_O, NULL},
{NULL}
};
Expand Down Expand Up @@ -2472,6 +2530,12 @@ test_create_module_from_initfunc(void)
L"import embedded_ext;"
L"print(embedded_ext);"
L"print(f'{embedded_ext.executed=}');"
// Same name, different initfuncs: the first one wins
L"spec = spec_from_loader('same_name', StaticExtensionImporter);"
L"a = create_static_module.create_module(spec);"
L"b = create_static_module.create_same_name_b(spec);"
L"print(f'{a.which=} {b.which=} {a is b=}');"
L"print(f'{create_static_module.initfunc_calls()=}');"
};
PyConfig config;
if (PyImport_AppendInittab("create_static_module",
Expand All @@ -2496,7 +2560,10 @@ test_create_module_from_initfunc(void)
" return None\n"
" @staticmethod\n"
" def create_module(spec):\n"
" return create_static_module.create_module(spec)\n"
" mod = create_static_module.create_module(spec)\n"
" print(f'created {spec.name}: '\n"
" f'in sys.modules={spec.name in sys.modules}')\n"
" return mod\n"
" @staticmethod\n"
" def exec_module(module):\n"
" create_static_module.exec_module(module)\n"
Expand Down
Loading