[Python-checkins] bpo-46730: Add more info to @property AttributeError messages (GH-31311)

sweeneyde webhook-mailer at python.org
Wed Feb 16 02:07:45 EST 2022


https://github.com/python/cpython/commit/0cb765b2cec9b020224af016a83bf35c45b71932
commit: 0cb765b2cec9b020224af016a83bf35c45b71932
branch: main
author: Alex-Blade <44120047+Alex-Blade at users.noreply.github.com>
committer: sweeneyde <36520290+sweeneyde at users.noreply.github.com>
date: 2022-02-16T02:07:34-05:00
summary:

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`.

files:
A Misc/NEWS.d/next/Core and Builtins/2022-02-14-21-04-43.bpo-46730.rYJ1w5.rst
M Doc/howto/descriptor.rst
M Lib/test/test_property.py
M Misc/ACKS
M Objects/descrobject.c

diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst
index f8b1e00d96fad..4f6389e3b2d48 100644
--- a/Doc/howto/descriptor.rst
+++ b/Doc/howto/descriptor.rst
@@ -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):
         ...
diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py
index 7f3813fc8cd15..7d1c4a1e12880 100644
--- a/Lib/test/test_property.py
+++ b/Lib/test/test_property.py
@@ -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
diff --git a/Misc/ACKS b/Misc/ACKS
index 6df339c3e145b..64bd91d7d6905 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -985,6 +985,7 @@ Erno Kuusela
 Ross Lagerwall
 Cameron Laird
 Loïc Lajeanne
+Alexander Lakeev
 David Lam
 Thomas Lamb
 Valerie Lambert
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-02-14-21-04-43.bpo-46730.rYJ1w5.rst b/Misc/NEWS.d/next/Core and Builtins/2022-02-14-21-04-43.bpo-46730.rYJ1w5.rst
new file mode 100644
index 0000000000000..473b595545370
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-02-14-21-04-43.bpo-46730.rYJ1w5.rst	
@@ -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.
diff --git a/Objects/descrobject.c b/Objects/descrobject.c
index 95162683edd7d..9379ad65de43e 100644
--- a/Objects/descrobject.c
+++ b/Objects/descrobject.c
@@ -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;
     }



More information about the Python-checkins mailing list