summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Hilton-Balfe <gobot1234yt@gmail.com>2023-07-22 00:24:26 (GMT)
committerGitHub <noreply@github.com>2023-07-22 00:24:26 (GMT)
commitcdeb1a6caad5e3067f01d6058238803b8517f9de (patch)
tree952950c42ffd25901e8b9319120f5e57ab9507bd
parent41ca16455188db806bfc7037058e8ecff2755e6c (diff)
downloadcpython-cdeb1a6caad5e3067f01d6058238803b8517f9de.zip
cpython-cdeb1a6caad5e3067f01d6058238803b8517f9de.tar.gz
cpython-cdeb1a6caad5e3067f01d6058238803b8517f9de.tar.bz2
gh-96663: Add a better error message for __dict__-less classes setattr (#103232)
-rw-r--r--Lib/test/test_class.py18
-rw-r--r--Lib/test/test_descrtut.py2
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2023-04-04-00-40-04.gh-issue-96663.PdR9hK.rst1
-rw-r--r--Objects/object.c16
4 files changed, 32 insertions, 5 deletions
diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py
index 894e0ca..bb35bae 100644
--- a/Lib/test/test_class.py
+++ b/Lib/test/test_class.py
@@ -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):
diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py
index 7796031..13e3ea4 100644
--- a/Lib/test/test_descrtut.py
+++ b/Lib/test/test_descrtut.py
@@ -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
index 0000000..cb806b5
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-04-04-00-40-04.gh-issue-96663.PdR9hK.rst
@@ -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.
diff --git a/Objects/object.c b/Objects/object.c
index d30e048..bfbc871 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -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);