mirror of https://github.com/python/cpython
bpo-43857: Improve the AttributeError message when deleting a missing attribute (#25424)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
parent
43b135f94e
commit
a95138b2c5
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Improve the :exc:`AttributeError` message when deleting a missing attribute.
|
||||
Patch by Géry Ogam.
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue