mirror of
https://github.com/python/cpython.git
synced 2025-12-15 21:44:50 +00:00
bpo-46730: Add more info to @property AttributeError messages (GH-31311)
On `obj.read_only_property = x`, raise `AttributeError: property 'read_only_property' of 'A' object has no setter`.
This commit is contained in:
parent
4d8a515d19
commit
0cb765b2ce
5 changed files with 40 additions and 22 deletions
|
|
@ -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):
|
||||
|
|
@ -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):
|
||||
...
|
||||
|
|
|
|||
|
|
@ -322,27 +322,27 @@ class _PropertyUnreachableAttribute:
|
|||
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
|
||||
|
|
|
|||
|
|
@ -985,6 +985,7 @@ Erno Kuusela
|
|||
Ross Lagerwall
|
||||
Cameron Laird
|
||||
Loïc Lajeanne
|
||||
Alexander Lakeev
|
||||
David Lam
|
||||
Thomas Lamb
|
||||
Valerie Lambert
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
||||
*/
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue