From cdeb1a6caad5e3067f01d6058238803b8517f9de Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sat, 22 Jul 2023 01:24:26 +0100 Subject: gh-96663: Add a better error message for __dict__-less classes setattr (#103232) --- Lib/test/test_class.py | 18 +++++++++++++++++- Lib/test/test_descrtut.py | 2 +- .../2023-04-04-00-40-04.gh-issue-96663.PdR9hK.rst | 1 + Objects/object.c | 16 +++++++++++++--- 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-04-04-00-40-04.gh-issue-96663.PdR9hK.rst 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 "", 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); -- cgit v0.12