From 06df3a72dfe462c8fe4eac60dce0ef059b1738f8 Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Sun, 13 Feb 2022 04:12:59 +0300 Subject: [PATCH 01/12] bpo-46730: Make @property AttributeError unique and sensible The property decorator raises AttributeErrors with messages that neither help to identify the problem nor lead to a possible solution. This commit proposes changes in error messages to address the issue. --- Doc/howto/descriptor.rst | 8 ++++---- Lib/test/test_property.py | 6 +++--- Objects/descrobject.c | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index f8b1e00d96fadc..217810b9bb264f 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'unreadable property {self._name}') 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"no setter was defined for property {self._name}") 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"no deleter was defined for property {self._name}") 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: no setter was defined for property >>> mark.location = 'Mars' Traceback (most recent call last): ... diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 7f3813fc8cd15e..485e361c608a63 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -322,15 +322,15 @@ 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("unreadable property")): 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("no setter was defined for property")): 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("no deleter was defined for property")): del self.obj.foo diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 95162683edd7d1..e074391e303c44 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1586,9 +1586,9 @@ 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); + PyErr_Format(PyExc_AttributeError, "unreadable property %R", gs->prop_name); } else { - PyErr_SetString(PyExc_AttributeError, "unreadable attribute"); + PyErr_SetString(PyExc_AttributeError, "unreadable property"); } return NULL; @@ -1614,15 +1614,15 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value) if (gs->prop_name != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? - "can't delete attribute %R" : - "can't set attribute %R", + "no deleter was defined for property %R" : + "no setter was defined for property %R", gs->prop_name); } else { PyErr_SetString(PyExc_AttributeError, value == NULL ? - "can't delete attribute" : - "can't set attribute"); + "no deleter was defined for property" : + "no setter was defined for property"); } return -1; } From 0b1ac6412b49295caeb6c3a1b6043799bdedf0fe Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Sun, 13 Feb 2022 15:10:41 +0300 Subject: [PATCH 02/12] bpo-46730: Add more details on property origin to the AttributeError This is an improvement to the initial patch for the bpo-46730. It adds additional information on class that owns the property that caused an AttributeError. --- Lib/test/test_property.py | 14 +++++++------- Lib/test/test_sys.py | 2 +- Objects/descrobject.c | 40 ++++++++++++++++++++++++++++++--------- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 485e361c608a63..72562d8f46c553 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -314,35 +314,35 @@ class _PropertyUnreachableAttribute: obj = None cls = None - def _format_exc_msg(self, msg): - return self.msg_format.format(msg) + def _format_exc_msg(self, *msg): + return self.msg_format.format(*msg) @classmethod def setUpClass(cls): cls.obj = cls.cls() def test_get_property(self): - with self.assertRaisesRegex(AttributeError, self._format_exc_msg("unreadable property")): + 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("no setter was defined for property")): + 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("no deleter was defined for property")): + 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 = "^property 'foo' originating from 'cls' {}$" class cls: foo = property() class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^{}$" + msg_format = "^property {}$" class cls: pass diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 21d7ccbf709666..2b4511c980f92e 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1450,7 +1450,7 @@ def getx(self): return self.__x def setx(self, value): self.__x = value def delx(self): del self.__x x = property(getx, setx, delx, "") - check(x, size('5Pi')) + check(x, size('6Pi')) # PyCapsule # XXX # rangeiterator diff --git a/Objects/descrobject.c b/Objects/descrobject.c index e074391e303c44..dc28131302661f 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1485,6 +1485,7 @@ typedef struct { PyObject *prop_del; PyObject *prop_doc; PyObject *prop_name; + PyObject *owner_name; int getter_doc; } propertyobject; @@ -1544,11 +1545,15 @@ property_set_name(PyObject *self, PyObject *args) { } propertyobject *prop = (propertyobject *)self; + PyObject *owner_name = PyObject_GetAttrString(PyTuple_GET_ITEM(args, 0), "__name__"); PyObject *name = PyTuple_GET_ITEM(args, 1); Py_XINCREF(name); Py_XSETREF(prop->prop_name, name); + Py_XINCREF(owner_name); + Py_XSETREF(prop->owner_name, owner_name); + Py_RETURN_NONE; } @@ -1572,6 +1577,7 @@ property_dealloc(PyObject *self) Py_XDECREF(gs->prop_del); Py_XDECREF(gs->prop_doc); Py_XDECREF(gs->prop_name); + Py_XDECREF(gs->owner_name); Py_TYPE(self)->tp_free(self); } @@ -1585,10 +1591,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 property %R", gs->prop_name); + if (gs->prop_name != NULL && gs->owner_name != NULL) { + PyErr_Format(PyExc_AttributeError, + "property %R originating from %R has no getter", + gs->prop_name, + gs->owner_name); + } else if (gs->prop_name != NULL) { + PyErr_Format(PyExc_AttributeError, "property %R has no getter", gs->prop_name); } else { - PyErr_SetString(PyExc_AttributeError, "unreadable property"); + PyErr_SetString(PyExc_AttributeError, "property has no getter"); } return NULL; @@ -1611,18 +1622,26 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value) } if (func == NULL) { - if (gs->prop_name != NULL) { + if (gs->prop_name != NULL && gs->owner_name != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? - "no deleter was defined for property %R" : - "no setter was defined for property %R", - gs->prop_name); + "property %R originating from %R has no deleter" : + "property %R originating from %R has no setter", + gs->prop_name, + gs->owner_name); + } + else if (gs->prop_name != NULL) { + PyErr_Format(PyExc_AttributeError, + value == NULL ? + "property %R has no deleter" : + "property %R has no setter", + gs->prop_name); } else { PyErr_SetString(PyExc_AttributeError, value == NULL ? - "no deleter was defined for property" : - "no setter was defined for property"); + "property has no deleter" : + "property has no setter"); } return -1; } @@ -1680,6 +1699,9 @@ property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *del) Py_XINCREF(pold->prop_name); Py_XSETREF(((propertyobject *) new)->prop_name, pold->prop_name); + + Py_XINCREF(pold->owner_name); + Py_XSETREF(((propertyobject *) new)->owner_name, pold->owner_name); return new; } From e86f544de62e18e95d18fb95f146131ecbee95c0 Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Mon, 14 Feb 2022 14:19:54 +0300 Subject: [PATCH 03/12] bpo-46730: Make AttributeError show self.__qualname__ instead of owner type --- Lib/test/test_property.py | 4 ++-- Lib/test/test_sys.py | 2 +- Objects/descrobject.c | 49 +++++++++++++++------------------------ 3 files changed, 22 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 72562d8f46c553..8d44e56c739c57 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -335,14 +335,14 @@ def test_del_property(self): class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^property 'foo' originating from 'cls' {}$" + msg_format = "^property 'foo' in object of type 'PropertyUnreachableAttributeWithName.cls' {}$" class cls: foo = property() class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^property {}$" + msg_format = "^property in object of type 'PropertyUnreachableAttributeNoName.cls' {}$" class cls: pass diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 2b4511c980f92e..21d7ccbf709666 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1450,7 +1450,7 @@ def getx(self): return self.__x def setx(self, value): self.__x = value def delx(self): del self.__x x = property(getx, setx, delx, "") - check(x, size('6Pi')) + check(x, size('5Pi')) # PyCapsule # XXX # rangeiterator diff --git a/Objects/descrobject.c b/Objects/descrobject.c index dc28131302661f..3272e59ed2c989 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1485,7 +1485,6 @@ typedef struct { PyObject *prop_del; PyObject *prop_doc; PyObject *prop_name; - PyObject *owner_name; int getter_doc; } propertyobject; @@ -1545,15 +1544,11 @@ property_set_name(PyObject *self, PyObject *args) { } propertyobject *prop = (propertyobject *)self; - PyObject *owner_name = PyObject_GetAttrString(PyTuple_GET_ITEM(args, 0), "__name__"); PyObject *name = PyTuple_GET_ITEM(args, 1); Py_XINCREF(name); Py_XSETREF(prop->prop_name, name); - Py_XINCREF(owner_name); - Py_XSETREF(prop->owner_name, owner_name); - Py_RETURN_NONE; } @@ -1577,7 +1572,6 @@ property_dealloc(PyObject *self) Py_XDECREF(gs->prop_del); Py_XDECREF(gs->prop_doc); Py_XDECREF(gs->prop_name); - Py_XDECREF(gs->owner_name); Py_TYPE(self)->tp_free(self); } @@ -1591,15 +1585,15 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type) propertyobject *gs = (propertyobject *)self; if (gs->prop_get == NULL) { - if (gs->prop_name != NULL && gs->owner_name != NULL) { + if (gs->prop_name != NULL) { PyErr_Format(PyExc_AttributeError, - "property %R originating from %R has no getter", + "property %R in object of type %R has no getter", gs->prop_name, - gs->owner_name); - } else if (gs->prop_name != NULL) { - PyErr_Format(PyExc_AttributeError, "property %R has no getter", gs->prop_name); + PyType_GetQualName(Py_TYPE(obj))); } else { - PyErr_SetString(PyExc_AttributeError, "property has no getter"); + PyErr_Format(PyExc_AttributeError, + "property in object of type %R has no getter", + PyType_GetQualName(Py_TYPE(obj))); } return NULL; @@ -1622,26 +1616,24 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value) } if (func == NULL) { - if (gs->prop_name != NULL && gs->owner_name != NULL) { + if (gs->prop_name != NULL && obj != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? - "property %R originating from %R has no deleter" : - "property %R originating from %R has no setter", + "property %R in object of type %R has no deleter" : + "property %R in object of type %R has no setter", gs->prop_name, - gs->owner_name); - } - else if (gs->prop_name != NULL) { + PyType_GetQualName(Py_TYPE(obj))); + } else if (obj != NULL) { PyErr_Format(PyExc_AttributeError, - value == NULL ? - "property %R has no deleter" : - "property %R has no setter", - gs->prop_name); - } - else { - PyErr_SetString(PyExc_AttributeError, value == NULL ? - "property has no deleter" : - "property has no setter"); + "property in object of type %R has no deleter" : + "property in object of type %R has no setter", + PyType_GetQualName(Py_TYPE(obj))); + } else { + PyErr_SetString(PyExc_AttributeError, + value == NULL ? + "property has no deleter" : + "property has no setter"); } return -1; } @@ -1699,9 +1691,6 @@ property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *del) Py_XINCREF(pold->prop_name); Py_XSETREF(((propertyobject *) new)->prop_name, pold->prop_name); - - Py_XINCREF(pold->owner_name); - Py_XSETREF(((propertyobject *) new)->owner_name, pold->owner_name); return new; } From 921d3e50149024dfa0db9d3b1cc8f9099da8a1ab Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Mon, 14 Feb 2022 15:32:01 +0300 Subject: [PATCH 04/12] bpo-46730: Adjusted documentation according to the latest changes --- Doc/howto/descriptor.rst | 6 +++--- Objects/descrobject.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index 217810b9bb264f..5b14f477cbafa9 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 property {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"no setter was defined for property {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"no deleter was defined for property {self._name}") + raise AttributeError(f"property '{self._name}' has no deleter") self.fdel(obj) def getter(self, fget): diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 3272e59ed2c989..3d044e22b127e6 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) */ From f1cae0c1aca92ffaa13491d9fba3a48dd02386e2 Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Mon, 14 Feb 2022 23:15:27 +0300 Subject: [PATCH 05/12] bpo-46730: Fix regex dot in tests Properly escape dot character and return `_format_exc_msg` function to its initial state. --- Lib/test/test_property.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 8d44e56c739c57..c011fb77aa4b15 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -314,8 +314,8 @@ class _PropertyUnreachableAttribute: obj = None cls = None - def _format_exc_msg(self, *msg): - return self.msg_format.format(*msg) + def _format_exc_msg(self, msg): + return self.msg_format.format(msg) @classmethod def setUpClass(cls): @@ -335,14 +335,14 @@ def test_del_property(self): class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^property 'foo' in object of type 'PropertyUnreachableAttributeWithName.cls' {}$" + msg_format = r"^property 'foo' in object of type 'PropertyUnreachableAttributeWithName\.cls' {}$" class cls: foo = property() class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^property in object of type 'PropertyUnreachableAttributeNoName.cls' {}$" + msg_format = "^property in object of type 'PropertyUnreachableAttributeNoName\.cls' {}$" class cls: pass From 83cae8812838450621a8ecb0717b34d490c1fcda Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Mon, 14 Feb 2022 23:26:38 +0300 Subject: [PATCH 06/12] bpo-46730: Make changes comply with PEP 7 --- Objects/descrobject.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 3d044e22b127e6..0697371c4e8519 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1590,7 +1590,8 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type) "property %R in object of type %R has no getter", gs->prop_name, PyType_GetQualName(Py_TYPE(obj))); - } else { + } + else { PyErr_Format(PyExc_AttributeError, "property in object of type %R has no getter", PyType_GetQualName(Py_TYPE(obj))); @@ -1623,13 +1624,15 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value) "property %R in object of type %R has no setter", gs->prop_name, PyType_GetQualName(Py_TYPE(obj))); - } else if (obj != NULL) { + } + else if (obj != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? "property in object of type %R has no deleter" : "property in object of type %R has no setter", PyType_GetQualName(Py_TYPE(obj))); - } else { + } + else { PyErr_SetString(PyExc_AttributeError, value == NULL ? "property has no deleter" : From 759427709ae8db023400aa9baafa6ec9c784ede3 Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Mon, 14 Feb 2022 23:45:16 +0300 Subject: [PATCH 07/12] bpo-46730: Change preposition from "in" to "of" in error --- Lib/test/test_property.py | 4 ++-- Objects/descrobject.c | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index c011fb77aa4b15..6ec6688b57aed3 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -335,14 +335,14 @@ def test_del_property(self): class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = r"^property 'foo' in object of type 'PropertyUnreachableAttributeWithName\.cls' {}$" + msg_format = r"^property 'foo' of object of type 'PropertyUnreachableAttributeWithName\.cls' {}$" class cls: foo = property() class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^property in object of type 'PropertyUnreachableAttributeNoName\.cls' {}$" + msg_format = "^property of object of type 'PropertyUnreachableAttributeNoName\.cls' {}$" class cls: pass diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 0697371c4e8519..236a923b5bc6a2 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1587,13 +1587,13 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type) if (gs->prop_get == NULL) { if (gs->prop_name != NULL) { PyErr_Format(PyExc_AttributeError, - "property %R in object of type %R has no getter", + "property %R of object of type %R has no getter", gs->prop_name, PyType_GetQualName(Py_TYPE(obj))); } else { PyErr_Format(PyExc_AttributeError, - "property in object of type %R has no getter", + "property of object of type %R has no getter", PyType_GetQualName(Py_TYPE(obj))); } @@ -1620,16 +1620,16 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value) if (gs->prop_name != NULL && obj != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? - "property %R in object of type %R has no deleter" : - "property %R in object of type %R has no setter", + "property %R of object of type %R has no deleter" : + "property %R of object of type %R has no setter", gs->prop_name, PyType_GetQualName(Py_TYPE(obj))); } else if (obj != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? - "property in object of type %R has no deleter" : - "property in object of type %R has no setter", + "property of object of type %R has no deleter" : + "property of object of type %R has no setter", PyType_GetQualName(Py_TYPE(obj))); } else { From 7399805b578e49e533eae1bf63e54798a891344f Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 21:04:44 +0000 Subject: [PATCH 08/12] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core and Builtins/2022-02-14-21-04-43.bpo-46730.rYJ1w5.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-02-14-21-04-43.bpo-46730.rYJ1w5.rst 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 00000000000000..473b595545370b --- /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. From 236e579a6c84fae92d3c53312a41111c0d990b82 Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Tue, 15 Feb 2022 00:29:00 +0300 Subject: [PATCH 09/12] Update ACKS --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 6df339c3e145bf..64bd91d7d69053 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 From 1de53bba3c4066b9d44f7edfe7c0796fc4688336 Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Tue, 15 Feb 2022 21:51:41 +0300 Subject: [PATCH 10/12] bpo-46730: Remove "of type of" from the message --- Lib/test/test_property.py | 4 ++-- Objects/descrobject.c | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 6ec6688b57aed3..e7b7effb920585 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -335,14 +335,14 @@ def test_del_property(self): class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = r"^property 'foo' of object of type 'PropertyUnreachableAttributeWithName\.cls' {}$" + msg_format = r"^property 'foo' of object 'PropertyUnreachableAttributeWithName\.cls' {}$" class cls: foo = property() class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^property of object of type 'PropertyUnreachableAttributeNoName\.cls' {}$" + msg_format = "^property of object 'PropertyUnreachableAttributeNoName\.cls' {}$" class cls: pass diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 236a923b5bc6a2..9000b026086979 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1587,13 +1587,13 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type) if (gs->prop_get == NULL) { if (gs->prop_name != NULL) { PyErr_Format(PyExc_AttributeError, - "property %R of object of type %R has no getter", + "property %R of object %R has no getter", gs->prop_name, PyType_GetQualName(Py_TYPE(obj))); } else { PyErr_Format(PyExc_AttributeError, - "property of object of type %R has no getter", + "property of object %R has no getter", PyType_GetQualName(Py_TYPE(obj))); } @@ -1620,16 +1620,16 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value) if (gs->prop_name != NULL && obj != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? - "property %R of object of type %R has no deleter" : - "property %R of object of type %R has no setter", + "property %R of object %R has no deleter" : + "property %R of object %R has no setter", gs->prop_name, PyType_GetQualName(Py_TYPE(obj))); } else if (obj != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? - "property of object of type %R has no deleter" : - "property of object of type %R has no setter", + "property of object %R has no deleter" : + "property of object %R has no setter", PyType_GetQualName(Py_TYPE(obj))); } else { From 3d931a171136189f8f9d28898eecc669d2a6720e Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Tue, 15 Feb 2022 22:13:38 +0300 Subject: [PATCH 11/12] bpo-46730: Change the order of words --- Lib/test/test_property.py | 4 ++-- Objects/descrobject.c | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index e7b7effb920585..7d1c4a1e128805 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -335,14 +335,14 @@ def test_del_property(self): class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = r"^property 'foo' of object 'PropertyUnreachableAttributeWithName\.cls' {}$" + msg_format = r"^property 'foo' of 'PropertyUnreachableAttributeWithName\.cls' object {}$" class cls: foo = property() class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^property of object 'PropertyUnreachableAttributeNoName\.cls' {}$" + msg_format = "^property of 'PropertyUnreachableAttributeNoName\.cls' object {}$" class cls: pass diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 9000b026086979..9379ad65de43ef 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1587,13 +1587,13 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type) if (gs->prop_get == NULL) { if (gs->prop_name != NULL) { PyErr_Format(PyExc_AttributeError, - "property %R of object %R has no getter", + "property %R of %R object has no getter", gs->prop_name, PyType_GetQualName(Py_TYPE(obj))); } else { PyErr_Format(PyExc_AttributeError, - "property of object %R has no getter", + "property of %R object has no getter", PyType_GetQualName(Py_TYPE(obj))); } @@ -1620,16 +1620,16 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value) if (gs->prop_name != NULL && obj != NULL) { PyErr_Format(PyExc_AttributeError, value == NULL ? - "property %R of object %R has no deleter" : - "property %R of object %R has no setter", + "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 object %R has no deleter" : - "property of object %R has no setter", + "property of %R object has no deleter" : + "property of %R object has no setter", PyType_GetQualName(Py_TYPE(obj))); } else { From bc901754a3362af749d611fdb97d27e8cd7c9c30 Mon Sep 17 00:00:00 2001 From: Alexander Lakeev Date: Wed, 16 Feb 2022 03:28:45 +0300 Subject: [PATCH 12/12] bpo-46730: Fix doctest for property --- Doc/howto/descriptor.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index 5b14f477cbafa9..4f6389e3b2d487 100644 --- a/Doc/howto/descriptor.rst +++ b/Doc/howto/descriptor.rst @@ -1456,7 +1456,7 @@ attributes stored in ``__slots__``: >>> mark.dept = 'Space Pirate' Traceback (most recent call last): ... - AttributeError: no setter was defined for property + AttributeError: property 'dept' of 'Immutable' object has no setter >>> mark.location = 'Mars' Traceback (most recent call last): ...