Skip to content

Commit 9be59ec

Browse files
committed
Publish lazy imported packages on parent
1 parent 781eedb commit 9be59ec

File tree

8 files changed

+248
-1
lines changed

8 files changed

+248
-1
lines changed

Include/internal/pycore_interp_structs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ struct _import_state {
317317
int lazy_imports_mode;
318318
PyObject *lazy_imports_filter;
319319
PyObject *lazy_importing_modules;
320+
PyObject *lazy_modules;
320321
/* The global import lock. */
321322
_PyRecursiveMutex lock;
322323
/* diagnostic info in PyImport_ImportModuleLevelObject() */

Lib/importlib/_bootstrap.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,12 @@ def _find_and_load_unlocked(name, import_):
13591359
except AttributeError:
13601360
msg = f"Cannot set an attribute on {parent!r} for child module {child!r}"
13611361
_warnings.warn(msg, ImportWarning)
1362+
# Set attributes to lazy submodules on the module.
1363+
try:
1364+
_imp._set_lazy_attributes(module, name)
1365+
except Exception as e:
1366+
msg = f"Cannot set lazy attributes on {name!r}: {e!r}"
1367+
_warnings.warn(msg, ImportWarning)
13621368
return module
13631369

13641370

Lib/test/test_import/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2706,6 +2706,15 @@ def test_eager_import_func(self):
27062706
f = test.test_import.data.lazy_imports.eager_import_func.f
27072707
self.assertEqual(type(f()), type(sys))
27082708

2709+
def test_lazy_import_pkg(self):
2710+
try:
2711+
import test.test_import.data.lazy_imports.lazy_import_pkg
2712+
except ImportError as e:
2713+
self.fail('lazy import failed')
2714+
2715+
self.assertTrue("test.test_import.data.lazy_imports.pkg" in sys.modules)
2716+
self.assertTrue("test.test_import.data.lazy_imports.pkg.bar" in sys.modules)
2717+
27092718

27102719
class TestSinglePhaseSnapshot(ModuleSnapshot):
27112720
"""A representation of a single-phase init module for testing.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lazy import test.test_import.data.lazy_imports.pkg.bar
2+
x = test.test_import.data.lazy_imports.pkg.bar.f
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
x = 42
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def f(): pass
2+

Python/clinic/import.c.h

Lines changed: 36 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/import.c

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "pycore_pylifecycle.h"
2121
#include "pycore_pymem.h" // _PyMem_DefaultRawFree()
2222
#include "pycore_pystate.h" // _PyInterpreterState_GET()
23+
#include "pycore_setobject.h" // _PySet_NextEntry()
2324
#include "pycore_sysmodule.h" // _PySys_ClearAttrString()
2425
#include "pycore_time.h" // _PyTime_AsMicroseconds()
2526
#include "pycore_unicodeobject.h" // _PyUnicode_AsUTF8NoNUL()
@@ -4129,6 +4130,114 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
41294130
return final_mod;
41304131
}
41314132

4133+
static PyObject *
4134+
get_mod_dict(PyObject *module)
4135+
{
4136+
if (PyModule_Check(module)) {
4137+
return Py_XNewRef(PyModule_GetDict(module));
4138+
}
4139+
4140+
return PyObject_GetAttr(module, &_Py_ID(__dict__));
4141+
}
4142+
4143+
static int
4144+
register_lazy_on_parent(PyThreadState *tstate, PyObject *name, PyObject *import_func)
4145+
{
4146+
int ret = -1;
4147+
PyObject *parent = NULL;
4148+
PyObject *child = NULL;
4149+
PyObject *parent_module = NULL;
4150+
PyObject *parent_dict = NULL;
4151+
PyObject *lazy_modules = tstate->interp->imports.lazy_modules;
4152+
if (lazy_modules == NULL) {
4153+
lazy_modules = tstate->interp->imports.lazy_modules = PyDict_New();
4154+
if (lazy_modules == NULL) {
4155+
return -1;
4156+
}
4157+
}
4158+
4159+
Py_INCREF(name);
4160+
while (true) {
4161+
Py_ssize_t dot = PyUnicode_FindChar(name, '.', 0, PyUnicode_GET_LENGTH(name), -1);
4162+
if (dot < 0) {
4163+
ret = 0;
4164+
goto done;
4165+
}
4166+
parent = PyUnicode_Substring(name, 0, dot);
4167+
/* If `parent` is NULL then this has hit the end of the import, no more
4168+
* "parent.child" in the import name. The entire import will be resolved
4169+
* lazily. */
4170+
if (parent == NULL) {
4171+
goto done;
4172+
}
4173+
Py_XDECREF(child);
4174+
child = PyUnicode_Substring(name, dot + 1, PyUnicode_GET_LENGTH(name));
4175+
if (child == NULL) {
4176+
goto done;
4177+
}
4178+
4179+
/* Add the lazy import for the child to the parent */
4180+
Py_XDECREF(parent_module);
4181+
parent_module = PyImport_GetModule(parent);
4182+
if (parent_module == NULL) {
4183+
if (PyErr_Occurred()) {
4184+
goto done;
4185+
}
4186+
4187+
// Record the child to be added when the parent is imported.
4188+
PyObject *lazy_submodules;
4189+
if (PyDict_GetItemRef(lazy_modules, parent, &lazy_submodules) < 0) {
4190+
goto done;
4191+
}
4192+
if (lazy_submodules == NULL) {
4193+
lazy_submodules = PySet_New(NULL);
4194+
if (lazy_submodules == NULL) {
4195+
goto done;
4196+
}
4197+
if (PyDict_SetItem(lazy_modules, parent, lazy_submodules) < 0) {
4198+
Py_DECREF(lazy_submodules);
4199+
goto done;
4200+
}
4201+
}
4202+
if (PySet_Add(lazy_submodules, child) < 0) {
4203+
Py_DECREF(lazy_submodules);
4204+
goto done;
4205+
}
4206+
Py_DECREF(lazy_submodules);
4207+
} else {
4208+
Py_XDECREF(parent_dict);
4209+
parent_dict = get_mod_dict(parent_module);
4210+
if (parent_dict == NULL) {
4211+
goto done;
4212+
}
4213+
if (PyDict_CheckExact(parent_dict) && !PyDict_Contains(parent_dict, child)) {
4214+
printf("!!! Adding lazy onto %s %s\n", PyUnicode_AsUTF8(parent), PyUnicode_AsUTF8(child));
4215+
PyObject *lazy_module_attr = _PyLazyImport_New(import_func, parent, child);
4216+
if (lazy_module_attr == NULL) {
4217+
goto done;
4218+
}
4219+
if (PyDict_SetItem(parent_dict, child, lazy_module_attr) < 0) {
4220+
Py_DECREF(lazy_module_attr);
4221+
goto done;
4222+
}
4223+
Py_DECREF(lazy_module_attr);
4224+
}
4225+
}
4226+
4227+
Py_DECREF(name);
4228+
name = parent;
4229+
parent = NULL;
4230+
}
4231+
4232+
done:
4233+
Py_XDECREF(parent_dict);
4234+
Py_XDECREF(parent_module);
4235+
Py_XDECREF(child);
4236+
Py_XDECREF(parent);
4237+
Py_XDECREF(name);
4238+
return ret;
4239+
}
4240+
41324241
PyObject *
41334242
_PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
41344243
PyObject *name, PyObject *import_func,
@@ -4144,6 +4253,14 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
41444253
_PyInterpreterFrame *frame = _PyEval_GetFrame();
41454254
assert(frame->f_globals == frame->f_locals); // should only be called in global scope
41464255

4256+
PyObject *mod = PyImport_GetModule(abs_name);
4257+
bool already_exists = mod != NULL;
4258+
Py_XDECREF(mod);
4259+
//if (mod != NULL) {
4260+
// Py_DECREF(abs_name);
4261+
// return mod;
4262+
//}
4263+
41474264
// Check if the filter disables the lazy import
41484265
PyObject *filter = LAZY_IMPORTS_FILTER(interp);
41494266
if (filter != NULL) {
@@ -4175,6 +4292,10 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
41754292
}
41764293

41774294
PyObject *res = _PyLazyImport_New(import_func, abs_name, fromlist);
4295+
if (!already_exists && register_lazy_on_parent(tstate, abs_name, import_func) < 0) {
4296+
Py_DECREF(res);
4297+
res = NULL;
4298+
}
41784299
Py_DECREF(abs_name);
41794300
return res;
41804301
}
@@ -5181,6 +5302,75 @@ _imp_set_lazy_imports_impl(PyObject *module, PyObject *enabled,
51815302
Py_RETURN_NONE;
51825303
}
51835304

5305+
/*[clinic input]
5306+
_imp._set_lazy_attributes
5307+
child_module: object
5308+
name: unicode
5309+
/
5310+
Sets attributes to lazy submodules on the module, as side effects.
5311+
[clinic start generated code]*/
5312+
5313+
static PyObject *
5314+
_imp__set_lazy_attributes_impl(PyObject *module, PyObject *child_module,
5315+
PyObject *name)
5316+
/*[clinic end generated code: output=bd34f2e16f215c29 input=d959fbfa236f4d59]*/
5317+
{
5318+
PyThreadState *tstate = _PyThreadState_GET();
5319+
PyObject *child_dict = NULL;
5320+
PyObject *ret = NULL;
5321+
PyObject *lazy_modules = tstate->interp->imports.lazy_modules;
5322+
if (lazy_modules != NULL) {
5323+
PyObject *lazy_submodules = PyDict_GetItemWithError(lazy_modules, name);
5324+
if (lazy_submodules == NULL) {
5325+
if (PyErr_Occurred()) {
5326+
goto error;
5327+
}
5328+
goto done;
5329+
}
5330+
5331+
child_dict = get_mod_dict(child_module);
5332+
if (child_dict == NULL || !PyDict_CheckExact(child_dict)) {
5333+
goto done;
5334+
}
5335+
PyObject *attr_name;
5336+
Py_ssize_t pos = 0;
5337+
Py_hash_t hash;
5338+
while (_PySet_NextEntry(lazy_submodules, &pos, &attr_name, &hash)) {
5339+
if (PyDict_Contains(child_dict, attr_name)) {
5340+
printf("!!!!!!!! Not replacing %s\n", PyUnicode_AsUTF8(attr_name));
5341+
continue;
5342+
}
5343+
PyObject *builtins = _PyEval_GetBuiltins(tstate);
5344+
PyObject *import_func;
5345+
if (PyMapping_GetOptionalItem(builtins, &_Py_ID(__import__), &import_func) < 0) {
5346+
goto error;
5347+
} else if (import_func == NULL) {
5348+
_PyErr_SetString(tstate, PyExc_ImportError, "__import__ not found");
5349+
goto error;
5350+
}
5351+
5352+
PyObject *lazy_module_attr = _PyLazyImport_New(import_func, name, attr_name);
5353+
if (lazy_module_attr == NULL) {
5354+
goto error;
5355+
}
5356+
5357+
if (PyDict_SetItem(child_dict, attr_name, lazy_module_attr) < 0) {
5358+
Py_DECREF(lazy_module_attr);
5359+
goto error;
5360+
}
5361+
Py_DECREF(lazy_module_attr);
5362+
}
5363+
if (PyDict_DelItem(lazy_modules, name) < 0) {
5364+
goto error;
5365+
}
5366+
}
5367+
done:
5368+
ret = Py_NewRef(Py_None);
5369+
5370+
error:
5371+
Py_XDECREF(child_dict);
5372+
return ret;
5373+
}
51845374

51855375
PyDoc_STRVAR(doc_imp,
51865376
"(Extremely) low-level import machinery bits as used by importlib.");
@@ -5206,6 +5396,7 @@ static PyMethodDef imp_methods[] = {
52065396
_IMP__FIX_CO_FILENAME_METHODDEF
52075397
_IMP_SOURCE_HASH_METHODDEF
52085398
_IMP_SET_LAZY_IMPORTS_METHODDEF
5399+
_IMP__SET_LAZY_ATTRIBUTES_METHODDEF
52095400
{NULL, NULL} /* sentinel */
52105401
};
52115402

0 commit comments

Comments
 (0)