Skip to content

Commit 2d0003c

Browse files
authored
GH-151672: __lazy_import__ always resolves to the module being imported (#151827)
1 parent 7ed9062 commit 2d0003c

3 files changed

Lines changed: 43 additions & 8 deletions

File tree

Lib/test/test_lazy_import/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,31 @@ def test_dunder_lazy_import_used(self):
564564
import test.test_lazy_import.data.dunder_lazy_import_used
565565
self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
566566

567+
@support.requires_subprocess()
568+
def test_dunder_lazy_import_fromlist_resolves_to_module(self):
569+
for fromlist in ["basic2", ("basic2",)]:
570+
with self.subTest(fromlist=fromlist):
571+
code = textwrap.dedent(f"""
572+
import sys
573+
import types
574+
575+
lazy = __lazy_import__("test.test_lazy_import.data", fromlist={fromlist!r})
576+
577+
def check():
578+
lazy_obj = globals()["lazy"]
579+
assert type(lazy_obj) is types.LazyImportType, lazy_obj
580+
assert "test.test_lazy_import.data.basic2" not in sys.modules
581+
582+
resolved = lazy_obj.resolve()
583+
assert type(resolved) is types.ModuleType, resolved
584+
assert "test.test_lazy_import.data.basic2" in sys.modules
585+
assert resolved.__name__ == "test.test_lazy_import.data"
586+
assert resolved.basic2.x == 42
587+
588+
check()
589+
""")
590+
assert_python_ok("-c", code)
591+
567592
def test_dunder_lazy_import_invalid_arguments(self):
568593
"""__lazy_import__ should reject invalid arguments."""
569594
for invalid_name in (b"", 123, None):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix an inconsistency where calling ``__lazy_import__`` with a string
2+
``fromlist`` would return a :class:`types.LazyImportType` that resolves to
3+
the named member, rather than the module being imported.

Python/import.c

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4539,7 +4539,7 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
45394539
}
45404540
if (fromlist == NULL) {
45414541
assert(!PyErr_Occurred());
4542-
fromlist = Py_NewRef(Py_None);
4542+
fromlist = Py_None;
45434543
}
45444544
PyObject *args[] = {modname, abs_name, fromlist};
45454545
PyObject *res = PyObject_Vectorcall(filter, args, 3, NULL);
@@ -4568,8 +4568,19 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
45684568
}
45694569

45704570
// here, 'filter' is either NULL or is equivalent to a borrowed reference
4571+
if (fromlist && PyUnicode_Check(fromlist)) {
4572+
fromlist = PyTuple_Pack(1, fromlist);
4573+
if (fromlist == NULL) {
4574+
Py_DECREF(abs_name);
4575+
return NULL;
4576+
}
4577+
}
4578+
else {
4579+
Py_XINCREF(fromlist);
4580+
}
45714581
PyObject *res = _PyLazyImport_New(frame, builtins, abs_name, fromlist);
45724582
if (res == NULL) {
4583+
Py_XDECREF(fromlist);
45734584
Py_DECREF(abs_name);
45744585
return NULL;
45754586
}
@@ -4580,13 +4591,7 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
45804591
goto error;
45814592
}
45824593

4583-
if (fromlist && PyUnicode_Check(fromlist)) {
4584-
if (register_from_lazy_on_parent(tstate, abs_name, fromlist) < 0) {
4585-
goto error;
4586-
}
4587-
}
4588-
else if (fromlist && PyTuple_Check(fromlist) &&
4589-
PyTuple_GET_SIZE(fromlist)) {
4594+
if (fromlist && PyTuple_Check(fromlist) && PyTuple_GET_SIZE(fromlist)) {
45904595
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(fromlist); i++) {
45914596
if (register_from_lazy_on_parent(tstate, abs_name,
45924597
PyTuple_GET_ITEM(fromlist, i)) < 0)
@@ -4599,9 +4604,11 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
45994604
goto error;
46004605
}
46014606

4607+
Py_XDECREF(fromlist);
46024608
Py_DECREF(abs_name);
46034609
return res;
46044610
error:
4611+
Py_XDECREF(fromlist);
46054612
Py_DECREF(abs_name);
46064613
Py_DECREF(res);
46074614
return NULL;

0 commit comments

Comments
 (0)