Skip to content

Commit a95138b

Browse files
bpo-43857: Improve the AttributeError message when deleting a missing attribute (#25424)
Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 43b135f commit a95138b

File tree

4 files changed

+62
-5
lines changed

4 files changed

+62
-5
lines changed

Lib/test/test_class.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,49 @@ class A:
611611
with self.assertRaises(TypeError):
612612
type.__setattr__(A, b'x', None)
613613

614+
def testTypeAttributeAccessErrorMessages(self):
615+
class A:
616+
pass
617+
618+
error_msg = "type object 'A' has no attribute 'x'"
619+
with self.assertRaisesRegex(AttributeError, error_msg):
620+
A.x
621+
with self.assertRaisesRegex(AttributeError, error_msg):
622+
del A.x
623+
624+
def testObjectAttributeAccessErrorMessages(self):
625+
class A:
626+
pass
627+
class B:
628+
y = 0
629+
__slots__ = ('z',)
630+
631+
error_msg = "'A' object has no attribute 'x'"
632+
with self.assertRaisesRegex(AttributeError, error_msg):
633+
A().x
634+
with self.assertRaisesRegex(AttributeError, error_msg):
635+
del A().x
636+
637+
error_msg = "'B' object has no attribute 'x'"
638+
with self.assertRaisesRegex(AttributeError, error_msg):
639+
B().x
640+
with self.assertRaisesRegex(AttributeError, error_msg):
641+
del B().x
642+
with self.assertRaisesRegex(AttributeError, error_msg):
643+
B().x = 0
644+
645+
error_msg = "'B' object attribute 'y' is read-only"
646+
with self.assertRaisesRegex(AttributeError, error_msg):
647+
del B().y
648+
with self.assertRaisesRegex(AttributeError, error_msg):
649+
B().y = 0
650+
651+
error_msg = 'z'
652+
with self.assertRaisesRegex(AttributeError, error_msg):
653+
B().z
654+
with self.assertRaisesRegex(AttributeError, error_msg):
655+
del B().z
656+
614657
def testConstructorErrorMessages(self):
615658
# bpo-31506: Improves the error message logic for object_new & object_init
616659

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve the :exc:`AttributeError` message when deleting a missing attribute.
2+
Patch by Géry Ogam.

Objects/dictobject.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5472,7 +5472,9 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
54725472
values->values[ix] = value;
54735473
if (old_value == NULL) {
54745474
if (value == NULL) {
5475-
PyErr_SetObject(PyExc_AttributeError, name);
5475+
PyErr_Format(PyExc_AttributeError,
5476+
"'%.100s' object has no attribute '%U'",
5477+
Py_TYPE(obj)->tp_name, name);
54765478
return -1;
54775479
}
54785480
_PyDictValues_AddToInsertionOrder(values, ix);

Objects/object.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,7 +1382,7 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
13821382
return -1;
13831383

13841384
Py_INCREF(name);
1385-
1385+
Py_INCREF(tp);
13861386
descr = _PyType_Lookup(tp, name);
13871387

13881388
if (descr != NULL) {
@@ -1426,11 +1426,21 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
14261426
res = PyDict_SetItem(dict, name, value);
14271427
Py_DECREF(dict);
14281428
}
1429-
if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
1430-
PyErr_SetObject(PyExc_AttributeError, name);
1431-
1429+
if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
1430+
if (PyType_IsSubtype(tp, &PyType_Type)) {
1431+
PyErr_Format(PyExc_AttributeError,
1432+
"type object '%.50s' has no attribute '%U'",
1433+
((PyTypeObject*)obj)->tp_name, name);
1434+
}
1435+
else {
1436+
PyErr_Format(PyExc_AttributeError,
1437+
"'%.100s' object has no attribute '%U'",
1438+
tp->tp_name, name);
1439+
}
1440+
}
14321441
done:
14331442
Py_XDECREF(descr);
1443+
Py_DECREF(tp);
14341444
Py_DECREF(name);
14351445
return res;
14361446
}

0 commit comments

Comments
 (0)