gh-96663: Add a better error message for __dict__-less classes setattr (#103232)
This commit is contained in:
parent
41ca164551
commit
cdeb1a6caa
@ -641,6 +641,14 @@ class ClassTests(unittest.TestCase):
|
|||||||
class B:
|
class B:
|
||||||
y = 0
|
y = 0
|
||||||
__slots__ = ('z',)
|
__slots__ = ('z',)
|
||||||
|
class C:
|
||||||
|
__slots__ = ("y",)
|
||||||
|
|
||||||
|
def __setattr__(self, name, value) -> None:
|
||||||
|
if name == "z":
|
||||||
|
super().__setattr__("y", 1)
|
||||||
|
else:
|
||||||
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
error_msg = "'A' object has no attribute 'x'"
|
error_msg = "'A' object has no attribute 'x'"
|
||||||
with self.assertRaisesRegex(AttributeError, error_msg):
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
||||||
@ -653,8 +661,16 @@ class ClassTests(unittest.TestCase):
|
|||||||
B().x
|
B().x
|
||||||
with self.assertRaisesRegex(AttributeError, error_msg):
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
||||||
del B().x
|
del B().x
|
||||||
with self.assertRaisesRegex(AttributeError, error_msg):
|
with self.assertRaisesRegex(
|
||||||
|
AttributeError,
|
||||||
|
"'B' object has no attribute 'x' and no __dict__ for setting new attributes"
|
||||||
|
):
|
||||||
B().x = 0
|
B().x = 0
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
AttributeError,
|
||||||
|
"'C' object has no attribute 'x'"
|
||||||
|
):
|
||||||
|
C().x = 0
|
||||||
|
|
||||||
error_msg = "'B' object attribute 'y' is read-only"
|
error_msg = "'B' object attribute 'y' is read-only"
|
||||||
with self.assertRaisesRegex(AttributeError, error_msg):
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
||||||
|
@ -139,7 +139,7 @@ instance variables cannot be assigned to:
|
|||||||
>>> a.x1 = 1
|
>>> a.x1 = 1
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<stdin>", line 1, in ?
|
File "<stdin>", line 1, in ?
|
||||||
AttributeError: 'defaultdict2' object has no attribute 'x1'
|
AttributeError: 'defaultdict2' object has no attribute 'x1' and no __dict__ for setting new attributes
|
||||||
>>>
|
>>>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
Add a better, more introspect-able error message when setting attributes on classes without a ``__dict__`` and no slot member for the attribute.
|
@ -1576,9 +1576,18 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
|
|||||||
}
|
}
|
||||||
if (dictptr == NULL) {
|
if (dictptr == NULL) {
|
||||||
if (descr == NULL) {
|
if (descr == NULL) {
|
||||||
PyErr_Format(PyExc_AttributeError,
|
if (tp->tp_setattro == PyObject_GenericSetAttr) {
|
||||||
"'%.100s' object has no attribute '%U'",
|
PyErr_Format(PyExc_AttributeError,
|
||||||
tp->tp_name, name);
|
"'%.100s' object has no attribute '%U' and no "
|
||||||
|
"__dict__ for setting new attributes",
|
||||||
|
tp->tp_name, name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_Format(PyExc_AttributeError,
|
||||||
|
"'%.100s' object has no attribute '%U'",
|
||||||
|
tp->tp_name, name);
|
||||||
|
}
|
||||||
|
set_attribute_error_context(obj, name);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PyErr_Format(PyExc_AttributeError,
|
PyErr_Format(PyExc_AttributeError,
|
||||||
@ -1611,6 +1620,7 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
|
|||||||
"'%.100s' object has no attribute '%U'",
|
"'%.100s' object has no attribute '%U'",
|
||||||
tp->tp_name, name);
|
tp->tp_name, name);
|
||||||
}
|
}
|
||||||
|
set_attribute_error_context(obj, name);
|
||||||
}
|
}
|
||||||
done:
|
done:
|
||||||
Py_XDECREF(descr);
|
Py_XDECREF(descr);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user