Skip to content

bpo-46730: Add more info to @property AttributeError messages #31311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 16, 2022
Merged
8 changes: 4 additions & 4 deletions Doc/howto/descriptor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -991,17 +991,17 @@ here is a pure Python equivalent:
if obj is None:
return self
if self.fget is None:
raise AttributeError(f'unreadable attribute {self._name}')
raise AttributeError(f"property '{self._name}' has no getter")
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError(f"can't set attribute {self._name}")
raise AttributeError(f"property '{self._name}' has no setter")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError(f"can't delete attribute {self._name}")
raise AttributeError(f"property '{self._name}' has no deleter")
self.fdel(obj)

def getter(self, fget):
Expand Down Expand Up @@ -1456,7 +1456,7 @@ attributes stored in ``__slots__``:
>>> mark.dept = 'Space Pirate'
Traceback (most recent call last):
...
AttributeError: can't set attribute
AttributeError: property 'dept' of 'Immutable' object has no setter
>>> mark.location = 'Mars'
Traceback (most recent call last):
...
Expand Down
10 changes: 5 additions & 5 deletions Lib/test/test_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,27 +322,27 @@ def setUpClass(cls):
cls.obj = cls.cls()

def test_get_property(self):
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("unreadable attribute")):
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")):
self.obj.foo

def test_set_property(self):
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't set attribute")):
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no setter")):
self.obj.foo = None

def test_del_property(self):
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't delete attribute")):
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no deleter")):
del self.obj.foo


class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase):
msg_format = "^{} 'foo'$"
msg_format = r"^property 'foo' of 'PropertyUnreachableAttributeWithName\.cls' object {}$"

class cls:
foo = property()


class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase):
msg_format = "^{}$"
msg_format = "^property of 'PropertyUnreachableAttributeNoName\.cls' object {}$"

class cls:
pass
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,7 @@ Erno Kuusela
Ross Lagerwall
Cameron Laird
Loïc Lajeanne
Alexander Lakeev
David Lam
Thomas Lamb
Valerie Lambert
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Message of AttributeError caused by getting, setting or deleting a property
without the corresponding function now mentions that the attribute is in fact
a property and also specifies type of the class that it belongs to.
40 changes: 27 additions & 13 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1463,17 +1463,17 @@ class property(object):
if inst is None:
return self
if self.__get is None:
raise AttributeError, "unreadable attribute"
raise AttributeError, "property has no getter"
return self.__get(inst)

def __set__(self, inst, value):
if self.__set is None:
raise AttributeError, "can't set attribute"
raise AttributeError, "property has no setter"
return self.__set(inst, value)

def __delete__(self, inst):
if self.__del is None:
raise AttributeError, "can't delete attribute"
raise AttributeError, "property has no deleter"
return self.__del(inst)

*/
Expand Down Expand Up @@ -1586,9 +1586,15 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
propertyobject *gs = (propertyobject *)self;
if (gs->prop_get == NULL) {
if (gs->prop_name != NULL) {
PyErr_Format(PyExc_AttributeError, "unreadable attribute %R", gs->prop_name);
} else {
PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
PyErr_Format(PyExc_AttributeError,
"property %R of %R object has no getter",
gs->prop_name,
PyType_GetQualName(Py_TYPE(obj)));
}
else {
PyErr_Format(PyExc_AttributeError,
"property of %R object has no getter",
PyType_GetQualName(Py_TYPE(obj)));
}

return NULL;
Expand All @@ -1611,18 +1617,26 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
}

if (func == NULL) {
if (gs->prop_name != NULL) {
if (gs->prop_name != NULL && obj != NULL) {
PyErr_Format(PyExc_AttributeError,
value == NULL ?
"can't delete attribute %R" :
"can't set attribute %R",
gs->prop_name);
"property %R of %R object has no deleter" :
"property %R of %R object has no setter",
gs->prop_name,
PyType_GetQualName(Py_TYPE(obj)));
}
else if (obj != NULL) {
PyErr_Format(PyExc_AttributeError,
value == NULL ?
"property of %R object has no deleter" :
"property of %R object has no setter",
PyType_GetQualName(Py_TYPE(obj)));
}
else {
PyErr_SetString(PyExc_AttributeError,
value == NULL ?
"can't delete attribute" :
"can't set attribute");
value == NULL ?
"property has no deleter" :
"property has no setter");
}
return -1;
}
Expand Down