]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-96663: Add a better error message for __dict__-less classes setattr (#103232)
authorJames Hilton-Balfe <gobot1234yt@gmail.com>
Sat, 22 Jul 2023 00:24:26 +0000 (01:24 +0100)
committerGitHub <noreply@github.com>
Sat, 22 Jul 2023 00:24:26 +0000 (17:24 -0700)
Lib/test/test_class.py
Lib/test/test_descrtut.py
Misc/NEWS.d/next/Core and Builtins/2023-04-04-00-40-04.gh-issue-96663.PdR9hK.rst [new file with mode: 0644]
Objects/object.c

index 894e0ca67deabc41707b02d8f9c452a40cdba9b5..bb35baea508c76bb35b06839f26ba5a01c7b2eee 100644 (file)
@@ -641,6 +641,14 @@ class ClassTests(unittest.TestCase):
         class B:
             y = 0
             __slots__ = ('z',)
+        class C:
+            __slots__ = ("y",)
+
+            def __setattr__(self, name, value) -> None:
+                if name == "z":
+                    super().__setattr__("y", 1)
+                else:
+                    super().__setattr__(name, value)
 
         error_msg = "'A' object has no attribute 'x'"
         with self.assertRaisesRegex(AttributeError, error_msg):
@@ -653,8 +661,16 @@ class ClassTests(unittest.TestCase):
             B().x
         with self.assertRaisesRegex(AttributeError, error_msg):
             del B().x
-        with self.assertRaisesRegex(AttributeError, error_msg):
+        with self.assertRaisesRegex(
+            AttributeError,
+            "'B' object has no attribute 'x' and no __dict__ for setting new attributes"
+        ):
             B().x = 0
+        with self.assertRaisesRegex(
+            AttributeError,
+            "'C' object has no attribute 'x'"
+        ):
+            C().x = 0
 
         error_msg = "'B' object attribute 'y' is read-only"
         with self.assertRaisesRegex(AttributeError, error_msg):
index 7796031ed0602f3deb7c2fe70f2cae55373b53b0..13e3ea41bdb76c5857cae335196851ea765032ea 100644 (file)
@@ -139,7 +139,7 @@ instance variables cannot be assigned to:
     >>> a.x1 = 1
     Traceback (most recent call last):
       File "<stdin>", line 1, in ?
-    AttributeError: 'defaultdict2' object has no attribute 'x1'
+    AttributeError: 'defaultdict2' object has no attribute 'x1' and no __dict__ for setting new attributes
     >>>
 
 """
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-04-04-00-40-04.gh-issue-96663.PdR9hK.rst b/Misc/NEWS.d/next/Core and Builtins/2023-04-04-00-40-04.gh-issue-96663.PdR9hK.rst
new file mode 100644 (file)
index 0000000..cb806b5
--- /dev/null
@@ -0,0 +1 @@
+Add a better, more introspect-able error message when setting attributes on classes without a ``__dict__`` and no slot member for the attribute.
index d30e048335ab63f607477f35db431312bb8d865e..bfbc87198f5b3c1f04f9542771572e0ae5a81459 100644 (file)
@@ -1576,9 +1576,18 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
         }
         if (dictptr == NULL) {
             if (descr == NULL) {
-                PyErr_Format(PyExc_AttributeError,
-                            "'%.100s' object has no attribute '%U'",
-                            tp->tp_name, name);
+                if (tp->tp_setattro == PyObject_GenericSetAttr) {
+                    PyErr_Format(PyExc_AttributeError,
+                                "'%.100s' object has no attribute '%U' and no "
+                                "__dict__ for setting new attributes",
+                                tp->tp_name, name);
+                }
+                else {
+                    PyErr_Format(PyExc_AttributeError,
+                                "'%.100s' object has no attribute '%U'",
+                                tp->tp_name, name);
+                }
+                set_attribute_error_context(obj, name);
             }
             else {
                 PyErr_Format(PyExc_AttributeError,
@@ -1611,6 +1620,7 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
                          "'%.100s' object has no attribute '%U'",
                          tp->tp_name, name);
         }
+        set_attribute_error_context(obj, name);
     }
   done:
     Py_XDECREF(descr);