Skip to content

Commit 0cb765b

Browse files
authored
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`.
1 parent 4d8a515 commit 0cb765b

File tree

5 files changed

+40
-22
lines changed

5 files changed

+40
-22
lines changed

Doc/howto/descriptor.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -991,17 +991,17 @@ here is a pure Python equivalent:
991991
if obj is None:
992992
return self
993993
if self.fget is None:
994-
raise AttributeError(f'unreadable attribute {self._name}')
994+
raise AttributeError(f"property '{self._name}' has no getter")
995995
return self.fget(obj)
996996

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

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

10071007
def getter(self, fget):
@@ -1456,7 +1456,7 @@ attributes stored in ``__slots__``:
14561456
>>> mark.dept = 'Space Pirate'
14571457
Traceback (most recent call last):
14581458
...
1459-
AttributeError: can't set attribute
1459+
AttributeError: property 'dept' of 'Immutable' object has no setter
14601460
>>> mark.location = 'Mars'
14611461
Traceback (most recent call last):
14621462
...

Lib/test/test_property.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,27 +322,27 @@ def setUpClass(cls):
322322
cls.obj = cls.cls()
323323

324324
def test_get_property(self):
325-
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("unreadable attribute")):
325+
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")):
326326
self.obj.foo
327327

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

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

336336

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

340340
class cls:
341341
foo = property()
342342

343343

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

347347
class cls:
348348
pass

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,7 @@ Erno Kuusela
985985
Ross Lagerwall
986986
Cameron Laird
987987
Loïc Lajeanne
988+
Alexander Lakeev
988989
David Lam
989990
Thomas Lamb
990991
Valerie Lambert
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Message of AttributeError caused by getting, setting or deleting a property
2+
without the corresponding function now mentions that the attribute is in fact
3+
a property and also specifies type of the class that it belongs to.

Objects/descrobject.c

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,17 +1463,17 @@ class property(object):
14631463
if inst is None:
14641464
return self
14651465
if self.__get is None:
1466-
raise AttributeError, "unreadable attribute"
1466+
raise AttributeError, "property has no getter"
14671467
return self.__get(inst)
14681468
14691469
def __set__(self, inst, value):
14701470
if self.__set is None:
1471-
raise AttributeError, "can't set attribute"
1471+
raise AttributeError, "property has no setter"
14721472
return self.__set(inst, value)
14731473
14741474
def __delete__(self, inst):
14751475
if self.__del is None:
1476-
raise AttributeError, "can't delete attribute"
1476+
raise AttributeError, "property has no deleter"
14771477
return self.__del(inst)
14781478
14791479
*/
@@ -1586,9 +1586,15 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
15861586
propertyobject *gs = (propertyobject *)self;
15871587
if (gs->prop_get == NULL) {
15881588
if (gs->prop_name != NULL) {
1589-
PyErr_Format(PyExc_AttributeError, "unreadable attribute %R", gs->prop_name);
1590-
} else {
1591-
PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
1589+
PyErr_Format(PyExc_AttributeError,
1590+
"property %R of %R object has no getter",
1591+
gs->prop_name,
1592+
PyType_GetQualName(Py_TYPE(obj)));
1593+
}
1594+
else {
1595+
PyErr_Format(PyExc_AttributeError,
1596+
"property of %R object has no getter",
1597+
PyType_GetQualName(Py_TYPE(obj)));
15921598
}
15931599

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

16131619
if (func == NULL) {
1614-
if (gs->prop_name != NULL) {
1620+
if (gs->prop_name != NULL && obj != NULL) {
16151621
PyErr_Format(PyExc_AttributeError,
16161622
value == NULL ?
1617-
"can't delete attribute %R" :
1618-
"can't set attribute %R",
1619-
gs->prop_name);
1623+
"property %R of %R object has no deleter" :
1624+
"property %R of %R object has no setter",
1625+
gs->prop_name,
1626+
PyType_GetQualName(Py_TYPE(obj)));
1627+
}
1628+
else if (obj != NULL) {
1629+
PyErr_Format(PyExc_AttributeError,
1630+
value == NULL ?
1631+
"property of %R object has no deleter" :
1632+
"property of %R object has no setter",
1633+
PyType_GetQualName(Py_TYPE(obj)));
16201634
}
16211635
else {
16221636
PyErr_SetString(PyExc_AttributeError,
1623-
value == NULL ?
1624-
"can't delete attribute" :
1625-
"can't set attribute");
1637+
value == NULL ?
1638+
"property has no deleter" :
1639+
"property has no setter");
16261640
}
16271641
return -1;
16281642
}

0 commit comments

Comments
 (0)