From: Géry Ogam Date: Thu, 5 May 2022 13:37:26 +0000 (+0200) Subject: bpo-43857: Improve the AttributeError message when deleting a missing attribute ... X-Git-Tag: v3.11.0b1~43 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a95138b2c5a3ba3d9a1a635566e22e5843b6a45c;p=thirdparty%2FPython%2Fcpython.git bpo-43857: Improve the AttributeError message when deleting a missing attribute (#25424) Co-authored-by: Jelle Zijlstra --- diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 7cf5e06d59e2..91c53b7c894c 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 000000000000..0db4333bf994 --- /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 063fd242e6d5..ebbd22ee7c14 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 6f2d9f89562a..d5f21b7c6aa1 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; }