]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-43857: Improve the AttributeError message when deleting a missing attribute ...
authorGéry Ogam <gery.ogam@gmail.com>
Thu, 5 May 2022 13:37:26 +0000 (15:37 +0200)
committerGitHub <noreply@github.com>
Thu, 5 May 2022 13:37:26 +0000 (06:37 -0700)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Lib/test/test_class.py
Misc/NEWS.d/next/Core and Builtins/2022-05-04-11-37-20.bpo-43857.WuX8p3.rst [new file with mode: 0644]
Objects/dictobject.c
Objects/object.c

index 7cf5e06d59e209a0b3cb433ebe1d2658d188245c..91c53b7c894ceb196736b26888878a1d27fa738d 100644 (file)
@@ -611,6 +611,49 @@ class ClassTests(unittest.TestCase):
         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 (file)
index 0000000..0db4333
--- /dev/null
@@ -0,0 +1,2 @@
+Improve the :exc:`AttributeError` message when deleting a missing attribute.
+Patch by Géry Ogam.
index 063fd242e6d582862f0fde402374b293c83edb84..ebbd22ee7c145ead02dd50741df1e5ce1908d348 100644 (file)
@@ -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);
index 6f2d9f89562a50e5b0ee5b271a8fdfa2c37855ea..d5f21b7c6aa19f0c8054d3a1ed557fc6c5cbea3c 100644 (file)
@@ -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;
 }