gh-154196: Improve `AttributeError` messages from unresolved lazy imports (#154688)

pablogsal <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/125ca2699228379c9be80ad8a9d5c3631fcac44f
commit: 125ca2699228379c9be80ad8a9d5c3631fcac44f
branch: main
author: Bartosz Sławecki <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-08-16T14:25:03+01:00
summary:

gh-154196: Improve `AttributeError` messages from unresolved lazy imports (#154688)

files:
A Misc/NEWS.d/next/Core_and_Builtins/2026-07-25-12-43-42.gh-issue-154196.0rAdob.rst
M Lib/test/test_lazy_import/__init__.py
M Objects/lazyimportobject.c

diff --git a/Lib/test/test_lazy_import/__init__.py b/Lib/test/test_lazy_import/__init__.py
index b12e209707a9de3..9147e788d7a81f2 100644
--- a/Lib/test/test_lazy_import/__init__.py
+++ b/Lib/test/test_lazy_import/__init__.py
@@ -277,6 +277,23 @@ def test_lazy_import_type_attributes_accessible(self):
         proc = assert_python_ok("-c", code)
         self.assertIn(b"<built-in method resolve of lazy_import object at", proc.out)
 
+    @support.requires_subprocess()
+    def test_lazy_import_type_attribute_error_message(self):
+        """Check that LazyImportType attribute error message is helpful."""
+        code = textwrap.dedent("""
+            lazy import asyncio
+            try:
+                globals()["asyncio"].Task
+            except AttributeError as exc:
+                assert str(exc) == (
+                    "cannot access attribute 'Task' "
+                    "on unresolved lazy import 'asyncio'"
+                ), repr(str(exc))
+            else:
+                assert False, 'AttributeError is not raised'
+        """)
+        assert_python_ok("-c", code)
+
 
 class SyntaxRestrictionTests(LazyImportTestCase):
     """Tests for syntax restrictions on lazy imports."""
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-25-12-43-42.gh-issue-154196.0rAdob.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-25-12-43-42.gh-issue-154196.0rAdob.rst
new file mode 100644
index 000000000000000..8f55e76a5c0dcd4
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-25-12-43-42.gh-issue-154196.0rAdob.rst
@@ -0,0 +1,2 @@
+Improve :exc:`AttributeError` messages from unresolved lazy imports. Patch
+by Bartosz Sławecki.
diff --git a/Objects/lazyimportobject.c b/Objects/lazyimportobject.c
index fa1eb25047d9617..8f7f3f98c291289 100644
--- a/Objects/lazyimportobject.c
+++ b/Objects/lazyimportobject.c
@@ -81,6 +81,29 @@ lazy_import_dealloc(PyObject *op)
     Py_TYPE(op)->tp_free(op);
 }
 
+/* Specialize the error message for failed attribute lookups. */
+static PyObject *
+lazy_import_getattro(PyObject *op, PyObject *name)
+{
+    PyObject *value = _PyObject_GenericGetAttrWithDict(op, name, NULL, /* suppress */1);
+    if (value == NULL) {
+        if (PyErr_Occurred()) {
+            // pass up non-AttributeError exception
+            return NULL;
+        }
+        PyObject *lz_name = _PyLazyImport_GetName(op);
+        if (lz_name == NULL) {
+            return NULL;
+        }
+        PyErr_Format(PyExc_AttributeError,
+                     "cannot access attribute %R on unresolved lazy import %R",
+                     name, lz_name);
+        Py_DECREF(lz_name);
+        return NULL;
+    }
+    return value;
+}
+
 static PyObject *
 lazy_import_name(PyLazyImportObject *m)
 {
@@ -149,6 +172,7 @@ PyTypeObject PyLazyImport_Type = {
     .tp_repr = lazy_import_repr,
     .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
     .tp_doc = lazy_import_doc,
+    .tp_getattro = lazy_import_getattro,
     .tp_traverse = lazy_import_traverse,
     .tp_clear = lazy_import_clear,
     .tp_methods = lazy_import_methods,

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.