[Python-checkins] bpo-43857: Improve the AttributeError message when deleting a missing attribute (#25424)

JelleZijlstra webhook-mailer at python.org
Thu May 5 09:37:31 EDT 2022


https://github.com/python/cpython/commit/a95138b2c5a3ba3d9a1a635566e22e5843b6a45c
commit: a95138b2c5a3ba3d9a1a635566e22e5843b6a45c
branch: main
author: Géry Ogam <gery.ogam at gmail.com>
committer: JelleZijlstra <jelle.zijlstra at gmail.com>
date: 2022-05-05T06:37:26-07:00
summary:

bpo-43857: Improve the AttributeError message when deleting a missing attribute (#25424)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra at gmail.com>

files:
A Misc/NEWS.d/next/Core and Builtins/2022-05-04-11-37-20.bpo-43857.WuX8p3.rst
M Lib/test/test_class.py
M Objects/dictobject.c
M Objects/object.c

diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py
index 7cf5e06d59e20..91c53b7c894ce 100644
--- a/Lib/test/test_class.py
+++ b/Lib/test/test_class.py
@@ -611,6 +611,49 @@ class A:
         with self.assertRaises(TypeError):
             type.__setattr__(A, b'x', None)
 
+    def testTypeAttributeAccessErrorMessages(self):
+        class A:
+            pass
+
+        error_msg = "type object 'A' has no attribute 'x'"
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            A.x
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            del A.x
+
+    def testObjectAttributeAccessErrorMessages(self):
+        class A:
+            pass
+        class B:
+            y = 0
+            __slots__ = ('z',)
+
+        error_msg = "'A' object has no attribute 'x'"
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            A().x
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            del A().x
+
+        error_msg = "'B' object has no attribute 'x'"
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            B().x
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            del B().x
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            B().x = 0
+
+        error_msg = "'B' object attribute 'y' is read-only"
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            del B().y
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            B().y = 0
+
+        error_msg = 'z'
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            B().z
+        with self.assertRaisesRegex(AttributeError, error_msg):
+            del B().z
+
     def testConstructorErrorMessages(self):
         # bpo-31506: Improves the error message logic for object_new & object_init
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-04-11-37-20.bpo-43857.WuX8p3.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-04-11-37-20.bpo-43857.WuX8p3.rst
new file mode 100644
index 0000000000000..0db4333bf9945
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-04-11-37-20.bpo-43857.WuX8p3.rst	
@@ -0,0 +1,2 @@
+Improve the :exc:`AttributeError` message when deleting a missing attribute.
+Patch by Géry Ogam.
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 063fd242e6d58..ebbd22ee7c145 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -5472,7 +5472,9 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
     values->values[ix] = value;
     if (old_value == NULL) {
         if (value == NULL) {
-            PyErr_SetObject(PyExc_AttributeError, name);
+            PyErr_Format(PyExc_AttributeError,
+                         "'%.100s' object has no attribute '%U'",
+                         Py_TYPE(obj)->tp_name, name);
             return -1;
         }
         _PyDictValues_AddToInsertionOrder(values, ix);
diff --git a/Objects/object.c b/Objects/object.c
index 6f2d9f89562a5..d5f21b7c6aa19 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -1382,7 +1382,7 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
         return -1;
 
     Py_INCREF(name);
-
+    Py_INCREF(tp);
     descr = _PyType_Lookup(tp, name);
 
     if (descr != NULL) {
@@ -1426,11 +1426,21 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
             res = PyDict_SetItem(dict, name, value);
         Py_DECREF(dict);
     }
-    if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
-        PyErr_SetObject(PyExc_AttributeError, name);
-
+    if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
+        if (PyType_IsSubtype(tp, &PyType_Type)) {
+            PyErr_Format(PyExc_AttributeError,
+                         "type object '%.50s' has no attribute '%U'",
+                         ((PyTypeObject*)obj)->tp_name, name);
+        }
+        else {
+            PyErr_Format(PyExc_AttributeError,
+                         "'%.100s' object has no attribute '%U'",
+                         tp->tp_name, name);
+        }
+    }
   done:
     Py_XDECREF(descr);
+    Py_DECREF(tp);
     Py_DECREF(name);
     return res;
 }



More information about the Python-checkins mailing list