Skip to content

Commit

Permalink
bpo-46730: Add more info to @Property AttributeError messages (GH-31311)
Browse files Browse the repository at this point in the history
On `obj.read_only_property = x`, raise `AttributeError: property 'read_only_property' of 'A' object has no setter`.
  • Loading branch information
Alex-Blade committed Feb 16, 2022
1 parent 4d8a515 commit 0cb765b
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 22 deletions.
8 changes: 4 additions & 4 deletions Doc/howto/descriptor.rst
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
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
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
@@ -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
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

0 comments on commit 0cb765b

Please sign in to comment.