diff --git a/Include/import.h b/Include/import.h index e538f89d48b..5a31c902f7f 100644 --- a/Include/import.h +++ b/Include/import.h @@ -59,7 +59,7 @@ PyAPI_FUNC(PyObject *) PyImport_ImportModuleLevel( #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03050000 CiAPI_FUNC(int) PyImport_IsLazyImportsEnabled(void); PyAPI_FUNC(PyObject *) PyImport_SetLazyImports( - PyObject *enabled, PyObject *excluding); + PyObject *enabled, PyObject *excluding, PyObject *eager); PyAPI_FUNC(PyObject *) PyImport_SetLazyImportsInModule( PyObject *enabled); PyObject * diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 8739b89cc6e..0a252c47513 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -251,6 +251,7 @@ struct _is { PyObject *builtins; // importlib module PyObject *importlib; + PyObject *excluding_modules; PyObject *eager_imports; /* Used in Modules/_threadmodule.c. */ diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py index 968aaf5f3ff..93a6b61a094 100644 --- a/Lib/importlib/__init__.py +++ b/Lib/importlib/__init__.py @@ -67,13 +67,17 @@ hydrate_lazy_objects = _imp.hydrate_lazy_objects -def set_lazy_imports(enable = True, *, excluding = None): +def set_lazy_imports(enable = True, /, excluding = None, eager = None): """Programmatic API for enabling lazy imports at runtime. - The optional argument `excluding` can be any container of strings; all imports - within modules whose full name is present in the container will be eager. + The optional argument `excluding` can be any container of strings; all the + modules whose full name is present in the container will be excluded from + having any lazy imports, so all the imports within such modules will be eager. + + The optional argument `eager` can be any container of strings; all imports for + which the import full name is present in the container will be imported eagerly. """ - _imp._set_lazy_imports(enable, excluding=excluding) + return _imp._set_lazy_imports(enable, excluding=excluding, eager=eager) def enable_lazy_imports_in_module(enable = True): diff --git a/Lib/test/lazyimports/immediate_set_lazy_import.py b/Lib/test/lazyimports/immediate_set_lazy_import.py index 91b5454276d..0e28b28cf8f 100644 --- a/Lib/test/lazyimports/immediate_set_lazy_import.py +++ b/Lib/test/lazyimports/immediate_set_lazy_import.py @@ -9,9 +9,14 @@ from test.lazyimports.data.metasyntactic import foo +importlib.set_lazy_imports(eager=["test.lazyimports.data.metasyntactic.plugh"]) + +from test.lazyimports.data.metasyntactic import plugh + importlib.set_lazy_imports(excluding=["test.lazyimports.immediate_set_lazy_import"]) from test.lazyimports.data.metasyntactic import waldo self.assertTrue(importlib.is_lazy_import(globals(), "foo")) +self.assertFalse(importlib.is_lazy_import(globals(), "plugh")) self.assertFalse(importlib.is_lazy_import(globals(), "waldo")) diff --git a/Python/clinic/import.c.h b/Python/clinic/import.c.h index 72a32811412..909a1ae34ae 100644 --- a/Python/clinic/import.c.h +++ b/Python/clinic/import.c.h @@ -488,7 +488,8 @@ _imp_is_lazy_import(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(_imp__set_lazy_imports__doc__, -"_set_lazy_imports($module, enabled=True, /, excluding=)\n" +"_set_lazy_imports($module, enabled=True, /,\n" +" excluding=, eager=)\n" "--\n" "\n" "Programmatic API for enabling lazy imports at runtime.\n" @@ -501,20 +502,21 @@ PyDoc_STRVAR(_imp__set_lazy_imports__doc__, static PyObject * _imp__set_lazy_imports_impl(PyObject *module, PyObject *enabled, - PyObject *excluding); + PyObject *excluding, PyObject *eager); static PyObject * _imp__set_lazy_imports(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; - static const char * const _keywords[] = {"", "excluding", NULL}; + static const char * const _keywords[] = {"", "excluding", "eager", NULL}; static _PyArg_Parser _parser = {NULL, _keywords, "_set_lazy_imports", 0}; - PyObject *argsbuf[2]; + PyObject *argsbuf[3]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; PyObject *enabled = Py_True; PyObject *excluding = NULL; + PyObject *eager = NULL; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 2, 0, argsbuf); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 3, 0, argsbuf); if (!args) { goto exit; } @@ -527,9 +529,15 @@ _imp__set_lazy_imports(PyObject *module, PyObject *const *args, Py_ssize_t nargs if (!noptargs) { goto skip_optional_pos; } - excluding = args[1]; + if (args[1]) { + excluding = args[1]; + if (!--noptargs) { + goto skip_optional_pos; + } + } + eager = args[2]; skip_optional_pos: - return_value = _imp__set_lazy_imports_impl(module, enabled, excluding); + return_value = _imp__set_lazy_imports_impl(module, enabled, excluding, eager); exit: return return_value; @@ -700,4 +708,4 @@ _imp_hydrate_lazy_objects(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef _IMP_EXEC_DYNAMIC_METHODDEF #define _IMP_EXEC_DYNAMIC_METHODDEF #endif /* !defined(_IMP_EXEC_DYNAMIC_METHODDEF) */ -/*[clinic end generated code: output=c844b9883a2d4a98 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=205e94ef323bb334 input=a9049054013a1b77]*/ diff --git a/Python/import.c b/Python/import.c index 208f67b1603..b078db9a93f 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1624,7 +1624,7 @@ add_lazy_submodule(PyObject *module, PyObject *name) } static int -add_lazy_modules(PyThreadState *tstate, PyObject *builtins, PyObject *name) +add_lazy_modules(PyThreadState *tstate, PyObject *builtins, PyObject *name, PyObject *fromlist) { int ret = 1; assert(tstate->interp->lazy_modules != NULL); @@ -1634,7 +1634,41 @@ add_lazy_modules(PyThreadState *tstate, PyObject *builtins, PyObject *name) PyObject *child = NULL; PyObject *parent_module = NULL; PyObject *parent_dict = NULL; - PyObject *lazy_submodules = PyDict_GetItemWithError(lazy_modules, name); + PyObject *lazy_submodules; + + if (tstate->interp->eager_imports != NULL) { + int found = PySequence_Contains(tstate->interp->eager_imports, name); + if (found < 0) { + goto error; + } + if (found) { + ret = 0; /* If the module is flagged as eager import, load eagerly */ + goto end; + } + if (fromlist != NULL && fromlist != Py_None) { + assert(PyTuple_CheckExact(fromlist)); + Py_ssize_t size = PyTuple_GET_SIZE(fromlist); + for (Py_ssize_t i = 0; i < size; ++i) { + PyObject* item = PyTuple_GET_ITEM(fromlist, i); + assert(PyUnicode_Check(item)); + PyObject *from_name = PyUnicode_FromFormat("%U.%U", name, item); + if (from_name == NULL) { + goto error; + } + found = PySequence_Contains(tstate->interp->eager_imports, from_name); + Py_DECREF(from_name); + if (found < 0) { + goto error; + } + if (found) { + ret = 0; /* If the module is flagged as eager import, load eagerly */ + goto end; + } + } + } + } + + lazy_submodules = PyDict_GetItemWithError(lazy_modules, name); if (lazy_submodules == NULL) { if (PyErr_Occurred()) { goto error; @@ -1649,7 +1683,7 @@ add_lazy_modules(PyThreadState *tstate, PyObject *builtins, PyObject *name) } Py_DECREF(lazy_submodules); } - PyObject *filter = tstate->interp->eager_imports; + PyObject *filter = tstate->interp->excluding_modules; while (1) { Py_ssize_t dot = PyUnicode_FindChar(name, '.', 0, PyUnicode_GET_LENGTH(name), -1); if (dot < 0) { @@ -1659,9 +1693,15 @@ add_lazy_modules(PyThreadState *tstate, PyObject *builtins, PyObject *name) if (parent == NULL) { goto error; } - if (filter != NULL && PySequence_Contains(filter, parent)) { - ret = 0; /* If the direct parent is eager, load eagerly */ - goto end; + if (filter != NULL) { + int found = PySequence_Contains(filter, parent); + if (found < 0) { + goto error; + } + if (found) { + ret = 0; /* If the direct parent is eager, load eagerly */ + goto end; + } } filter = NULL; Py_XDECREF(child); @@ -1808,7 +1848,7 @@ _PyImport_LazyImportName(PyObject *builtins, PyObject *globals, PyObject *locals Py_INCREF(abs_name); } - int lazy = add_lazy_modules(tstate, builtins, abs_name); + int lazy = add_lazy_modules(tstate, builtins, abs_name, fromlist); if (lazy < 0) { goto error; } @@ -2657,7 +2697,7 @@ closest_module_frame(PyFrameObject *frame) } PyObject * -PyImport_SetLazyImports(PyObject *enabled, PyObject *excluding) +PyImport_SetLazyImports(PyObject *enabled, PyObject *excluding, PyObject *eager) { PyObject *result = NULL; PyInterpreterState *interp = _PyInterpreterState_GET(); @@ -2665,8 +2705,9 @@ PyImport_SetLazyImports(PyObject *enabled, PyObject *excluding) assert(interp->lazy_imports != -1); result = PyTuple_Pack( - 2, + 3, interp->lazy_imports ? Py_True : Py_False, + interp->excluding_modules == NULL ? Py_None : interp->excluding_modules, interp->eager_imports == NULL ? Py_None : interp->eager_imports ); if (result == NULL) { @@ -2680,6 +2721,25 @@ PyImport_SetLazyImports(PyObject *enabled, PyObject *excluding) if (excluding != NULL) { if (Py_IsNone(excluding)) { + Py_XDECREF(interp->excluding_modules); + interp->excluding_modules = NULL; + } else { + PyObject *empty = PyUnicode_New(0, 0); + if (empty == NULL) { + goto error; + } + if (PySequence_Contains(excluding, empty) == -1) { + Py_DECREF(empty); + goto error; + } + Py_DECREF(empty); + Py_XDECREF(interp->excluding_modules); + interp->excluding_modules = Py_NewRef(excluding); + } + } + + if (eager != NULL) { + if (Py_IsNone(eager)) { Py_XDECREF(interp->eager_imports); interp->eager_imports = NULL; } else { @@ -2687,13 +2747,13 @@ PyImport_SetLazyImports(PyObject *enabled, PyObject *excluding) if (empty == NULL) { goto error; } - if (PySequence_Contains(excluding, empty) == -1) { + if (PySequence_Contains(eager, empty) == -1) { Py_DECREF(empty); goto error; } Py_DECREF(empty); Py_XDECREF(interp->eager_imports); - interp->eager_imports = Py_NewRef(excluding); + interp->eager_imports = Py_NewRef(eager); } } @@ -2777,6 +2837,7 @@ _imp._set_lazy_imports enabled: object = True / excluding: object = NULL + eager: object = NULL Programmatic API for enabling lazy imports at runtime. @@ -2786,10 +2847,10 @@ within which all imports will remain eager. static PyObject * _imp__set_lazy_imports_impl(PyObject *module, PyObject *enabled, - PyObject *excluding) -/*[clinic end generated code: output=bb6e4196f8cf4569 input=c16f11904690a50f]*/ + PyObject *excluding, PyObject *eager) +/*[clinic end generated code: output=77575489d11f5806 input=6fbccf14b8258afb]*/ { - return PyImport_SetLazyImports(enabled, excluding); + return PyImport_SetLazyImports(enabled, excluding, eager); } /*[clinic input] @@ -3293,7 +3354,7 @@ _PyImport_IsLazyImportsEnabled(PyThreadState *tstate) if (lazy_imports) { PyObject *modname = _PyDict_GetItemIdWithError(frame->f_globals, &PyId___name__); if (modname != NULL && modname != Py_None) { - PyObject *filter = tstate->interp->eager_imports; + PyObject *filter = tstate->interp->excluding_modules; if (filter != NULL && PySequence_Contains(filter, modname)) { lazy_imports = 0; /* Check imports explicitely set as eager */ } diff --git a/Python/pystate.c b/Python/pystate.c index 76dac944a93..e20e85ea8ac 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -275,6 +275,7 @@ PyInterpreterState_New(void) interp->audit_hooks = NULL; interp->lazy_imports = 0; + interp->excluding_modules = NULL; interp->eager_imports = NULL; interp->lazy_modules = NULL; @@ -316,6 +317,7 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) Py_CLEAR(interp->builtins_copy); Py_CLEAR(interp->importlib); Py_CLEAR(interp->import_func); + Py_CLEAR(interp->excluding_modules); Py_CLEAR(interp->eager_imports); Py_CLEAR(interp->lazy_modules); Py_CLEAR(interp->dict);