From 014e47df4c27534ad3e1bd4d358bfe1421c8571c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Thu, 15 Apr 2021 18:22:07 +0200 Subject: [PATCH 01/12] Fix the AttributeError message for deletion of a missing attribute --- Objects/object.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Objects/object.c b/Objects/object.c index 854cc85b1cfa46..9f86303c707c27 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1389,7 +1389,9 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name, Py_DECREF(dict); } if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) - PyErr_SetObject(PyExc_AttributeError, name); + PyErr_Format(PyExc_AttributeError, + "'%.100s' object has no attribute '%U'", + tp->tp_name, name); done: Py_XDECREF(descr); From 42da011393f286f11d939ae6837206512a57cbed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 11:11:21 +0200 Subject: [PATCH 02/12] Remove duplicate test (see #25863) --- Lib/test/test_class.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 7cf5e06d59e209..a0110791f3957f 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -604,13 +604,6 @@ def add(self, other): with self.assertRaises(TypeError): A() + 1 - def testSetattrNonStringName(self): - class A: - pass - - with self.assertRaises(TypeError): - type.__setattr__(A, b'x', None) - def testConstructorErrorMessages(self): # bpo-31506: Improves the error message logic for object_new & object_init From e8a7b27306175df82e26d640c3e1aa09193f30c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 11:12:50 +0200 Subject: [PATCH 03/12] Add tests of error messages for attribute access --- Lib/test/test_class.py | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index a0110791f3957f..50ba2e56b1b0c7 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -604,6 +604,49 @@ def add(self, other): with self.assertRaises(TypeError): A() + 1 + 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 From 4b63adae71a651564c54e9b8dda1c5f8b7a2db58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 11:54:30 +0200 Subject: [PATCH 04/12] Create 2022-05-04-11-37-20.bpo-43857.WuX8p3.rst --- .../Core and Builtins/2022-05-04-11-37-20.bpo-43857.WuX8p3.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-05-04-11-37-20.bpo-43857.WuX8p3.rst 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 00000000000000..0db4333bf9945e --- /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. From ad05ba393f4f62816818d526dc6f41b6a77936fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 17:57:55 +0200 Subject: [PATCH 05/12] Restore a test incorrectly removed --- Lib/test/test_class.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 50ba2e56b1b0c7..6579169ed49bd6 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -604,6 +604,13 @@ def add(self, other): with self.assertRaises(TypeError): A() + 1 + def testSetattrNonStringName(self): + class A: + pass + + with self.assertRaises(TypeError): + type.__setattr__(A, b'x', None) + def testTypeAttributeAccessErrorMessages(self): class A: pass From 935eea5b078b33b406d5c594b0b1270d439e493d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 17:59:20 +0200 Subject: [PATCH 06/12] Update test_class.py --- Lib/test/test_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 6579169ed49bd6..91c53b7c894ceb 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -609,7 +609,7 @@ class A: pass with self.assertRaises(TypeError): - type.__setattr__(A, b'x', None) + type.__setattr__(A, b'x', None) def testTypeAttributeAccessErrorMessages(self): class A: From 1cb97fc149f83e68d103aecb6ac6c06df75fdca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 19:22:25 +0200 Subject: [PATCH 07/12] Update dictobject.c --- Objects/dictobject.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 063fd242e6d582..2a72f3ad142984 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5472,7 +5472,10 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values, values->values[ix] = value; if (old_value == NULL) { if (value == NULL) { - PyErr_SetObject(PyExc_AttributeError, name); + PyTypeObject *tp = Py_TYPE(obj); + PyErr_Format(PyExc_AttributeError, + "'%.100s' object has no attribute '%U'", + tp->tp_name, name); return -1; } _PyDictValues_AddToInsertionOrder(values, ix); From 8dd6bb04426b0207c13378c44b6287d2b1ae1b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 21:04:07 +0200 Subject: [PATCH 08/12] Update object.c --- Objects/object.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index c97e893ac68302..4d321524784278 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1427,9 +1427,10 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name, Py_DECREF(dict); } if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) + PyTypeObject *type = (PyTypeObject*)obj; PyErr_Format(PyExc_AttributeError, - "'%.100s' object has no attribute '%U'", - tp->tp_name, name); + "type object '%.50s' has no attribute '%U'", + type->tp_name, name); done: Py_XDECREF(descr); From 128d6e53c74d87ac18ebf56bd8a0fc170784db83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 21:13:02 +0200 Subject: [PATCH 09/12] Update object.c --- Objects/object.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 4d321524784278..324dcba71bd788 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1427,10 +1427,9 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name, Py_DECREF(dict); } if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) - PyTypeObject *type = (PyTypeObject*)obj; PyErr_Format(PyExc_AttributeError, "type object '%.50s' has no attribute '%U'", - type->tp_name, name); + ((PyTypeObject*)obj)->tp_name, name); done: Py_XDECREF(descr); From e8f2cc72057fb3052166f26fe484b2fb35ee9568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 21:15:02 +0200 Subject: [PATCH 10/12] Update dictobject.c --- Objects/dictobject.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 2a72f3ad142984..ebbd22ee7c145e 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5472,10 +5472,9 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values, values->values[ix] = value; if (old_value == NULL) { if (value == NULL) { - PyTypeObject *tp = Py_TYPE(obj); PyErr_Format(PyExc_AttributeError, "'%.100s' object has no attribute '%U'", - tp->tp_name, name); + Py_TYPE(obj)->tp_name, name); return -1; } _PyDictValues_AddToInsertionOrder(values, ix); From 4391f887c7cc14109c5d19b8935e6e500cfed2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 4 May 2022 23:13:33 +0200 Subject: [PATCH 11/12] Update object.c --- Objects/object.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 324dcba71bd788..0a18949a419804 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1426,11 +1426,18 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name, res = PyDict_SetItem(dict, name, value); Py_DECREF(dict); } - if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) - PyErr_Format(PyExc_AttributeError, - "type object '%.50s' has no attribute '%U'", - ((PyTypeObject*)obj)->tp_name, 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(name); From 6f3ca3290c9bd9a0ff546ab391fd754d45ca2ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Thu, 5 May 2022 10:31:16 +0200 Subject: [PATCH 12/12] Update object.c --- Objects/object.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/object.c b/Objects/object.c index 0a18949a419804..61e50647c2c477 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) { @@ -1440,6 +1440,7 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name, } done: Py_XDECREF(descr); + Py_DECREF(tp); Py_DECREF(name); return res; }