Skip to content

Commit f977cd3

Browse files
committed
Chain every deferred IMPORT_FROM onto the previous placeholder
`lazy from a import b` now records its lookup the same way as `import a.b as c`, so a placeholder holds either the module name and fromlist or the previous placeholder and an attribute name, and reification has a single path. The import passes only the name being resolved as the fromlist, so accessing b still does not import the other names' submodules.
1 parent 8341ca0 commit f977cd3

5 files changed

Lines changed: 40 additions & 42 deletions

File tree

‎Lib/test/test_lazy_import/__init__.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,22 @@ def test_accessing_one_name_leaves_others_as_proxies(self):
11771177
self.assertEqual(result.returncode, 0, f"stdout: {result.stdout}, stderr: {result.stderr}")
11781178
self.assertIn("OK", result.stdout)
11791179

1180+
def test_accessing_one_name_imports_only_its_submodule(self):
1181+
"""Accessing one name should not import the other names' submodules."""
1182+
code = textwrap.dedent("""
1183+
import sys
1184+
1185+
lazy from test.test_lazy_import.data.pkg import b, bar, broken
1186+
1187+
# Importing bar prints, and importing broken raises.
1188+
b.foo()
1189+
1190+
assert "test.test_lazy_import.data.pkg.bar" not in sys.modules
1191+
assert "test.test_lazy_import.data.pkg.broken" not in sys.modules
1192+
""")
1193+
rc, out, err = assert_python_ok("-c", code)
1194+
self.assertEqual(out, b"")
1195+
11801196
def test_all_names_reified_after_all_accessed(self):
11811197
"""All names should be reified after each is accessed."""
11821198
code = textwrap.dedent("""
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Submodule that raises an error during import
2+
raise ValueError("This module always fails to import")

‎Objects/lazyimportobject.c‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,6 @@ lazy_import_getattro(PyObject *op, PyObject *name)
104104
return value;
105105
}
106106

107-
static PyObject *lazy_import_name(PyLazyImportObject *m);
108-
109107
// The dotted name of the object that resolving the placeholder returns.
110108
static PyObject *
111109
lazy_import_path(PyLazyImportObject *m)
@@ -120,7 +118,7 @@ lazy_import_path(PyLazyImportObject *m)
120118
return res;
121119
}
122120
if (m->lz_attr != NULL) {
123-
return lazy_import_name(m);
121+
return Py_NewRef(m->lz_from);
124122
}
125123
// __import__("a.b") returns the top-level package `a`.
126124
Py_ssize_t dot = PyUnicode_FindChar(
@@ -142,12 +140,7 @@ lazy_import_name(PyLazyImportObject *m)
142140
return lazy_import_path(m);
143141
}
144142
if (m->lz_attr != NULL) {
145-
if (PyUnicode_Check(m->lz_attr)) {
146-
return PyUnicode_FromFormat("%U.%U", m->lz_from, m->lz_attr);
147-
}
148-
else {
149-
return PyUnicode_FromFormat("%U...", m->lz_from);
150-
}
143+
return PyUnicode_FromFormat("%U...", m->lz_from);
151144
}
152145
return Py_NewRef(m->lz_from);
153146
}

‎Python/ceval.c‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3333,14 +3333,12 @@ _PyEval_LazyImportFrom(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObje
33333333
assert(PyUnicode_Check(name));
33343334
PyObject *ret;
33353335
PyLazyImportObject *d = (PyLazyImportObject *)v;
3336-
3337-
if (d->lz_attr == NULL || PyUnicode_Check(d->lz_attr)) {
3338-
// `import a.b.c as d`: record the lookup, to replay once the
3339-
// import has run.
3340-
return _PyLazyImport_New(frame, d->lz_builtins, v, name);
3336+
PyObject *mod = NULL;
3337+
// Only `from a import b` can take b off an already imported a;
3338+
// `import a.b as c` has to import a.b first.
3339+
if (d->lz_attr != NULL && PyTuple_Check(d->lz_attr)) {
3340+
mod = PyImport_GetModule(d->lz_from);
33413341
}
3342-
3343-
PyObject *mod = PyImport_GetModule(d->lz_from);
33443342
if (mod != NULL) {
33453343
// Check if the module already has the attribute, if so, resolve it
33463344
// eagerly.
@@ -3360,7 +3358,7 @@ _PyEval_LazyImportFrom(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObje
33603358
Py_DECREF(mod);
33613359
}
33623360

3363-
return _PyLazyImport_New(frame, d->lz_builtins, d->lz_from, name);
3361+
return _PyLazyImport_New(frame, d->lz_builtins, v, name);
33643362
}
33653363

33663364
#define CANNOT_CATCH_MSG "catching classes that do not inherit from "\

‎Python/import.c‎

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3904,20 +3904,14 @@ static PyObject *
39043904
lazy_import_replay_from(PyThreadState *tstate, PyObject *mod,
39053905
PyLazyImportObject *lz)
39063906
{
3907-
PyObject *from;
3908-
if (PyLazyImport_CheckExact(lz->lz_from)) {
3909-
from = lazy_import_replay_from(
3910-
tstate, mod, (PyLazyImportObject *)lz->lz_from);
3911-
if (from == NULL) {
3912-
return NULL;
3913-
}
3914-
}
3915-
else if (lz->lz_attr != NULL && PyUnicode_Check(lz->lz_attr)) {
3916-
from = Py_NewRef(mod);
3917-
}
3918-
else {
3907+
if (!PyLazyImport_CheckExact(lz->lz_from)) {
39193908
return Py_NewRef(mod);
39203909
}
3910+
PyObject *from = lazy_import_replay_from(
3911+
tstate, mod, (PyLazyImportObject *)lz->lz_from);
3912+
if (from == NULL) {
3913+
return NULL;
3914+
}
39213915
PyObject *obj = _PyEval_ImportFrom(tstate, from, lz->lz_attr);
39223916
Py_DECREF(from);
39233917
return obj;
@@ -3935,9 +3929,10 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
39353929
PyLazyImportObject *lz = (PyLazyImportObject *)lazy_import;
39363930
PyInterpreterState *interp = tstate->interp;
39373931

3938-
// Walk back to the placeholder IMPORT_NAME left.
3939-
PyLazyImportObject *root = lz;
3932+
// Walk back to the placeholder IMPORT_NAME left, and the first lookup on it.
3933+
PyLazyImportObject *root = lz, *first = NULL;
39403934
while (PyLazyImport_CheckExact(root->lz_from)) {
3935+
first = root;
39413936
root = (PyLazyImportObject *)root->lz_from;
39423937
}
39433938

@@ -3988,17 +3983,11 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
39883983
}
39893984

39903985
if (root->lz_attr != NULL) {
3991-
if (PyUnicode_Check(root->lz_attr)) {
3992-
fromlist = PyTuple_New(1);
3993-
if (fromlist == NULL) {
3994-
goto error;
3995-
}
3996-
Py_INCREF(root->lz_attr);
3997-
PyTuple_SET_ITEM(fromlist, 0, root->lz_attr);
3998-
}
3999-
else {
4000-
Py_INCREF(root->lz_attr);
4001-
fromlist = root->lz_attr;
3986+
// `from a import b, c`: import only the name being resolved.
3987+
fromlist = first ? PyTuple_Pack(1, first->lz_attr)
3988+
: Py_NewRef(root->lz_attr);
3989+
if (fromlist == NULL) {
3990+
goto error;
40023991
}
40033992
}
40043993

0 commit comments

Comments
 (0)