bpo-27794: Add `name` attribute to `property` class (GH-23967)
This commit is contained in:
parent
ba0e49a464
commit
c56387f80c
|
@ -934,32 +934,42 @@ here is a pure Python equivalent:
|
||||||
if doc is None and fget is not None:
|
if doc is None and fget is not None:
|
||||||
doc = fget.__doc__
|
doc = fget.__doc__
|
||||||
self.__doc__ = doc
|
self.__doc__ = doc
|
||||||
|
self._name = ''
|
||||||
|
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
def __get__(self, obj, objtype=None):
|
def __get__(self, obj, objtype=None):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return self
|
return self
|
||||||
if self.fget is None:
|
if self.fget is None:
|
||||||
raise AttributeError("unreadable attribute")
|
raise AttributeError(f'unreadable attribute {self._name}')
|
||||||
return self.fget(obj)
|
return self.fget(obj)
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
def __set__(self, obj, value):
|
||||||
if self.fset is None:
|
if self.fset is None:
|
||||||
raise AttributeError("can't set attribute")
|
raise AttributeError(f"can't set attribute {self._name}")
|
||||||
self.fset(obj, value)
|
self.fset(obj, value)
|
||||||
|
|
||||||
def __delete__(self, obj):
|
def __delete__(self, obj):
|
||||||
if self.fdel is None:
|
if self.fdel is None:
|
||||||
raise AttributeError("can't delete attribute")
|
raise AttributeError(f"can't delete attribute {self._name}")
|
||||||
self.fdel(obj)
|
self.fdel(obj)
|
||||||
|
|
||||||
def getter(self, fget):
|
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):
|
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):
|
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::
|
.. testcode::
|
||||||
:hide:
|
:hide:
|
||||||
|
|
|
@ -204,6 +204,16 @@ class PropertyTests(unittest.TestCase):
|
||||||
return 'Second'
|
return 'Second'
|
||||||
self.assertEqual(A.__doc__, '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
|
# Issue 5890: subclasses of property do not preserve method __doc__ strings
|
||||||
class PropertySub(property):
|
class PropertySub(property):
|
||||||
|
@ -299,6 +309,46 @@ class PropertySubclassTests(unittest.TestCase):
|
||||||
self.assertEqual(Foo.spam.__doc__, "a new docstring")
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1329,7 +1329,7 @@ class SizeofTest(unittest.TestCase):
|
||||||
def setx(self, value): self.__x = value
|
def setx(self, value): self.__x = value
|
||||||
def delx(self): del self.__x
|
def delx(self): del self.__x
|
||||||
x = property(getx, setx, delx, "")
|
x = property(getx, setx, delx, "")
|
||||||
check(x, size('4Pi'))
|
check(x, size('5Pi'))
|
||||||
# PyCapsule
|
# PyCapsule
|
||||||
# XXX
|
# XXX
|
||||||
# rangeiterator
|
# rangeiterator
|
||||||
|
|
|
@ -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.
|
|
@ -1490,6 +1490,7 @@ typedef struct {
|
||||||
PyObject *prop_set;
|
PyObject *prop_set;
|
||||||
PyObject *prop_del;
|
PyObject *prop_del;
|
||||||
PyObject *prop_doc;
|
PyObject *prop_doc;
|
||||||
|
PyObject *prop_name;
|
||||||
int getter_doc;
|
int getter_doc;
|
||||||
} propertyobject;
|
} 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[] = {
|
static PyMethodDef property_methods[] = {
|
||||||
{"getter", property_getter, METH_O, getter_doc},
|
{"getter", property_getter, METH_O, getter_doc},
|
||||||
{"setter", property_setter, METH_O, setter_doc},
|
{"setter", property_setter, METH_O, setter_doc},
|
||||||
{"deleter", property_deleter, METH_O, deleter_doc},
|
{"deleter", property_deleter, METH_O, deleter_doc},
|
||||||
|
{"__set_name__", property_set_name, METH_VARARGS, set_name_doc},
|
||||||
{0}
|
{0}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1553,6 +1577,7 @@ property_dealloc(PyObject *self)
|
||||||
Py_XDECREF(gs->prop_set);
|
Py_XDECREF(gs->prop_set);
|
||||||
Py_XDECREF(gs->prop_del);
|
Py_XDECREF(gs->prop_del);
|
||||||
Py_XDECREF(gs->prop_doc);
|
Py_XDECREF(gs->prop_doc);
|
||||||
|
Py_XDECREF(gs->prop_name);
|
||||||
Py_TYPE(self)->tp_free(self);
|
Py_TYPE(self)->tp_free(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1566,7 +1591,12 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
|
||||||
|
|
||||||
propertyobject *gs = (propertyobject *)self;
|
propertyobject *gs = (propertyobject *)self;
|
||||||
if (gs->prop_get == NULL) {
|
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_SetString(PyExc_AttributeError, "unreadable attribute");
|
||||||
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1584,10 +1614,18 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
|
||||||
else
|
else
|
||||||
func = gs->prop_set;
|
func = gs->prop_set;
|
||||||
if (func == NULL) {
|
if (func == NULL) {
|
||||||
|
if (gs->prop_name != NULL) {
|
||||||
|
PyErr_Format(PyExc_AttributeError,
|
||||||
|
value == NULL ?
|
||||||
|
"can't delete attribute %R" :
|
||||||
|
"can't set attribute %R",
|
||||||
|
gs->prop_name);
|
||||||
|
} else {
|
||||||
PyErr_SetString(PyExc_AttributeError,
|
PyErr_SetString(PyExc_AttributeError,
|
||||||
value == NULL ?
|
value == NULL ?
|
||||||
"can't delete attribute" :
|
"can't delete attribute" :
|
||||||
"can't set attribute");
|
"can't set attribute");
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (value == NULL)
|
if (value == NULL)
|
||||||
|
@ -1634,6 +1672,9 @@ property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *del)
|
||||||
Py_DECREF(type);
|
Py_DECREF(type);
|
||||||
if (new == NULL)
|
if (new == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
Py_XINCREF(pold->prop_name);
|
||||||
|
Py_XSETREF(((propertyobject *) new)->prop_name, pold->prop_name);
|
||||||
return new;
|
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_set, fset);
|
||||||
Py_XSETREF(self->prop_del, fdel);
|
Py_XSETREF(self->prop_del, fdel);
|
||||||
Py_XSETREF(self->prop_doc, doc);
|
Py_XSETREF(self->prop_doc, doc);
|
||||||
|
Py_XSETREF(self->prop_name, NULL);
|
||||||
|
|
||||||
self->getter_doc = 0;
|
self->getter_doc = 0;
|
||||||
|
|
||||||
/* if no docstring given and the getter has one, use that one */
|
/* 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_set);
|
||||||
Py_VISIT(pp->prop_del);
|
Py_VISIT(pp->prop_del);
|
||||||
Py_VISIT(pp->prop_doc);
|
Py_VISIT(pp->prop_doc);
|
||||||
|
Py_VISIT(pp->prop_name);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue