Skip to content

Commit

Permalink
bpo-43857: Improve the AttributeError message when deleting a missing…
Browse files Browse the repository at this point in the history
… attribute (#25424)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
  • Loading branch information
geryogam and JelleZijlstra committed May 5, 2022
1 parent 43b135f commit a95138b
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 5 deletions.
43 changes: 43 additions & 0 deletions Lib/test/test_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,49 @@ class A:
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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve the :exc:`AttributeError` message when deleting a missing attribute.
Patch by Géry Ogam.
4 changes: 3 additions & 1 deletion Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 14 additions & 4 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit a95138b

Please sign in to comment.