From a95138b2c5a3ba3d9a1a635566e22e5843b6a45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Thu, 5 May 2022 15:37:26 +0200 Subject: bpo-43857: Improve the AttributeError message when deleting a missing attribute (#25424) Co-authored-by: Jelle Zijlstra --- Lib/test/test_class.py | 43 ++++++++++++++++++++++ .../2022-05-04-11-37-20.bpo-43857.WuX8p3.rst | 2 + Objects/dictobject.c | 4 +- Objects/object.c | 18 +++++++-- 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-05-04-11-37-20.bpo-43857.WuX8p3.rst diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 7cf5e06..91c53b7 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -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 index 0000000..0db4333 --- /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 063fd24..ebbd22e 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 6f2d9f8..d5f21b7 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; } -- cgit v0.12