Skip to content

Commit 9a55b78

Browse files
committed
Don't let module __getattr__ shadow a module's own lazy imports
When a module attribute was a lazy import placeholder, module attribute lookup called the module's __getattr__ before resolving it. This was needed while lazy from-imports published placeholders on the source module. That no longer happens, so the check only affected the module's own lazy imports: a catch-all __getattr__ hid them, unlike eager imports. Resolve the placeholder directly.
1 parent 5fecd44 commit 9a55b78

3 files changed

Lines changed: 14 additions & 23 deletions

File tree

‎Lib/test/test_lazy_import/__init__.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ def test_from_import_with_imported_module_getattr(self):
168168
""")
169169
assert_python_ok("-c", code)
170170

171+
@support.requires_subprocess()
172+
def test_module_getattr_does_not_shadow_own_lazy_import(self):
173+
"""Module __getattr__ should not shadow the module's own lazy imports."""
174+
code = textwrap.dedent("""
175+
import test.test_lazy_import.data.module_with_lazy_import_and_getattr as mod
176+
assert mod.basic2.__name__ == "test.test_lazy_import.data.basic2"
177+
assert mod.missing == "from_getattr:missing"
178+
""")
179+
assert_python_ok("-c", code)
180+
171181

172182
class GlobalLazyImportModeTests(LazyImportTestCase):
173183
"""Tests for sys.set_lazy_imports() global mode control."""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
lazy import test.test_lazy_import.data.basic2 as basic2
2+
3+
def __getattr__(name):
4+
return f"from_getattr:{name}"

‎Objects/moduleobject.c‎

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,29 +1362,6 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
13621362
}
13631363
if (attr) {
13641364
if (PyLazyImport_CheckExact(attr)) {
1365-
// gh-144957: Module __getattr__ should get a chance to provide
1366-
// the attribute before resolving a lazy import placeholder.
1367-
// Resolving __getattr__ itself must not invoke the hook.
1368-
getattr = NULL;
1369-
if (!_PyUnicode_EqualToASCIIString(name, "__getattr__") &&
1370-
module_get_resolved_dict_item(
1371-
m->md_dict, &_Py_ID(__getattr__), &getattr) < 0) {
1372-
Py_DECREF(attr);
1373-
return NULL;
1374-
}
1375-
if (getattr) {
1376-
PyObject *result = PyObject_CallOneArg(getattr, name);
1377-
Py_DECREF(getattr);
1378-
if (result != NULL) {
1379-
Py_DECREF(attr);
1380-
return result;
1381-
}
1382-
if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
1383-
Py_DECREF(attr);
1384-
return NULL;
1385-
}
1386-
PyErr_Clear();
1387-
}
13881365
PyThreadState *tstate = PyThreadState_GET();
13891366
if (suppress && _PyLazyImport_IsResolving(tstate, attr)) {
13901367
// Only direct reentry means this attribute is unavailable.

0 commit comments

Comments
 (0)