summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/howto/descriptor.rst22
-rw-r--r--Lib/test/test_property.py50
-rw-r--r--Lib/test/test_sys.py2
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2020-12-27-18-07-43.bpo-27794.sxgfGi.rst3
-rw-r--r--Objects/descrobject.c52
5 files changed, 118 insertions, 11 deletions
diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst
index ab5a573..d172c9b 100644
--- a/Doc/howto/descriptor.rst
+++ b/Doc/howto/descriptor.rst
@@ -934,32 +934,42 @@ here is a pure Python equivalent:
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
+ self._name = ''
+
+ def __set_name__(self, owner, name):
+ self._name = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
- raise AttributeError("unreadable attribute")
+ raise AttributeError(f'unreadable attribute {self._name}')
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
- raise AttributeError("can't set attribute")
+ raise AttributeError(f"can't set attribute {self._name}")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
- raise AttributeError("can't delete attribute")
+ raise AttributeError(f"can't delete attribute {self._name}")
self.fdel(obj)
def getter(self, fget):
- return type(self)(fget, self.fset, self.fdel, self.__doc__)
+ prop = type(self)(fget, self.fset, self.fdel, self.__doc__)
+ prop._name = self._name
+ return prop
def setter(self, fset):
- return type(self)(self.fget, fset, self.fdel, self.__doc__)
+ prop = type(self)(self.fget, fset, self.fdel, self.__doc__)
+ prop._name = self._name
+ return prop
def deleter(self, fdel):
- return type(self)(self.fget, self.fset, fdel, self.__doc__)
+ prop = type(self)(self.fget, self.fset, fdel, self.__doc__)
+ prop._name = self._name
+ return prop
.. testcode::
:hide:
diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py
index 172737a..7f3813f 100644
--- a/Lib/test/test_property.py
+++ b/Lib/test/test_property.py
@@ -204,6 +204,16 @@ class PropertyTests(unittest.TestCase):
return 'Second'
self.assertEqual(A.__doc__, 'Second')
+ def test_property_set_name_incorrect_args(self):
+ p = property()
+
+ for i in (0, 1, 3):
+ with self.assertRaisesRegex(
+ TypeError,
+ fr'^__set_name__\(\) takes 2 positional arguments but {i} were given$'
+ ):
+ p.__set_name__(*([0] * i))
+
# Issue 5890: subclasses of property do not preserve method __doc__ strings
class PropertySub(property):
@@ -299,6 +309,46 @@ class PropertySubclassTests(unittest.TestCase):
self.assertEqual(Foo.spam.__doc__, "a new docstring")
+class _PropertyUnreachableAttribute:
+ msg_format = None
+ obj = None
+ cls = None
+
+ 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 attribute")):
+ self.obj.foo
+
+ def test_set_property(self):
+ with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't set attribute")):
+ self.obj.foo = None
+
+ def test_del_property(self):
+ with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't delete attribute")):
+ del self.obj.foo
+
+
+class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase):
+ msg_format = "^{} 'foo'$"
+
+ class cls:
+ foo = property()
+
+
+class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase):
+ msg_format = "^{}$"
+
+ class cls:
+ pass
+
+ cls.foo = property()
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 3860656..3af5b11 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1329,7 +1329,7 @@ class SizeofTest(unittest.TestCase):
def setx(self, value): self.__x = value
def delx(self): del self.__x
x = property(getx, setx, delx, "")
- check(x, size('4Pi'))
+ check(x, size('5Pi'))
# PyCapsule
# XXX
# rangeiterator
diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-12-27-18-07-43.bpo-27794.sxgfGi.rst b/Misc/NEWS.d/next/Core and Builtins/2020-12-27-18-07-43.bpo-27794.sxgfGi.rst
new file mode 100644
index 0000000..0f66b4e
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2020-12-27-18-07-43.bpo-27794.sxgfGi.rst
@@ -0,0 +1,3 @@
+Improve the error message for failed writes/deletes to property objects.
+When possible, the attribute name is now shown. Patch provided by
+Yurii Karabas.
diff --git a/Objects/descrobject.c b/Objects/descrobject.c
index a8ce13c..16c695a 100644
--- a/Objects/descrobject.c
+++ b/Objects/descrobject.c
@@ -1490,6 +1490,7 @@ typedef struct {
PyObject *prop_set;
PyObject *prop_del;
PyObject *prop_doc;
+ PyObject *prop_name;
int getter_doc;
} propertyobject;
@@ -1535,10 +1536,33 @@ property_deleter(PyObject *self, PyObject *deleter)
}
+PyDoc_STRVAR(set_name_doc,
+ "Method to set name of a property.");
+
+static PyObject *
+property_set_name(PyObject *self, PyObject *args) {
+ if (PyTuple_GET_SIZE(args) != 2) {
+ PyErr_Format(
+ PyExc_TypeError,
+ "__set_name__() takes 2 positional arguments but %d were given",
+ PyTuple_GET_SIZE(args));
+ return NULL;
+ }
+
+ propertyobject *prop = (propertyobject *)self;
+ PyObject *name = PyTuple_GET_ITEM(args, 1);
+
+ Py_XINCREF(name);
+ Py_XSETREF(prop->prop_name, name);
+
+ Py_RETURN_NONE;
+}
+
static PyMethodDef property_methods[] = {
{"getter", property_getter, METH_O, getter_doc},
{"setter", property_setter, METH_O, setter_doc},
{"deleter", property_deleter, METH_O, deleter_doc},
+ {"__set_name__", property_set_name, METH_VARARGS, set_name_doc},
{0}
};
@@ -1553,6 +1577,7 @@ property_dealloc(PyObject *self)
Py_XDECREF(gs->prop_set);
Py_XDECREF(gs->prop_del);
Py_XDECREF(gs->prop_doc);
+ Py_XDECREF(gs->prop_name);
Py_TYPE(self)->tp_free(self);
}
@@ -1566,7 +1591,12 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
propertyobject *gs = (propertyobject *)self;
if (gs->prop_get == NULL) {
- PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
+ if (gs->prop_name != NULL) {
+ PyErr_Format(PyExc_AttributeError, "unreadable attribute %R", gs->prop_name);
+ } else {
+ PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
+ }
+
return NULL;
}
@@ -1584,10 +1614,18 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
else
func = gs->prop_set;
if (func == NULL) {
- PyErr_SetString(PyExc_AttributeError,
+ if (gs->prop_name != NULL) {
+ PyErr_Format(PyExc_AttributeError,
value == NULL ?
- "can't delete attribute" :
- "can't set attribute");
+ "can't delete attribute %R" :
+ "can't set attribute %R",
+ gs->prop_name);
+ } else {
+ PyErr_SetString(PyExc_AttributeError,
+ value == NULL ?
+ "can't delete attribute" :
+ "can't set attribute");
+ }
return -1;
}
if (value == NULL)
@@ -1634,6 +1672,9 @@ property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *del)
Py_DECREF(type);
if (new == NULL)
return NULL;
+
+ Py_XINCREF(pold->prop_name);
+ Py_XSETREF(((propertyobject *) new)->prop_name, pold->prop_name);
return new;
}
@@ -1695,6 +1736,8 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
Py_XSETREF(self->prop_set, fset);
Py_XSETREF(self->prop_del, fdel);
Py_XSETREF(self->prop_doc, doc);
+ Py_XSETREF(self->prop_name, NULL);
+
self->getter_doc = 0;
/* if no docstring given and the getter has one, use that one */
@@ -1769,6 +1812,7 @@ property_traverse(PyObject *self, visitproc visit, void *arg)
Py_VISIT(pp->prop_set);
Py_VISIT(pp->prop_del);
Py_VISIT(pp->prop_doc);
+ Py_VISIT(pp->prop_name);
return 0;
}