Skip to content

Commit c56387f

Browse files
authored
bpo-27794: Add name attribute to property class (GH-23967)
1 parent ba0e49a commit c56387f

File tree

5 files changed

+118
-11
lines changed

5 files changed

+118
-11
lines changed

Doc/howto/descriptor.rst

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -934,32 +934,42 @@ here is a pure Python equivalent:
934934
if doc is None and fget is not None:
935935
doc = fget.__doc__
936936
self.__doc__ = doc
937+
self._name = ''
938+
939+
def __set_name__(self, owner, name):
940+
self._name = name
937941

938942
def __get__(self, obj, objtype=None):
939943
if obj is None:
940944
return self
941945
if self.fget is None:
942-
raise AttributeError("unreadable attribute")
946+
raise AttributeError(f'unreadable attribute {self._name}')
943947
return self.fget(obj)
944948

945949
def __set__(self, obj, value):
946950
if self.fset is None:
947-
raise AttributeError("can't set attribute")
951+
raise AttributeError(f"can't set attribute {self._name}")
948952
self.fset(obj, value)
949953

950954
def __delete__(self, obj):
951955
if self.fdel is None:
952-
raise AttributeError("can't delete attribute")
956+
raise AttributeError(f"can't delete attribute {self._name}")
953957
self.fdel(obj)
954958

955959
def getter(self, fget):
956-
return type(self)(fget, self.fset, self.fdel, self.__doc__)
960+
prop = type(self)(fget, self.fset, self.fdel, self.__doc__)
961+
prop._name = self._name
962+
return prop
957963

958964
def setter(self, fset):
959-
return type(self)(self.fget, fset, self.fdel, self.__doc__)
965+
prop = type(self)(self.fget, fset, self.fdel, self.__doc__)
966+
prop._name = self._name
967+
return prop
960968

961969
def deleter(self, fdel):
962-
return type(self)(self.fget, self.fset, fdel, self.__doc__)
970+
prop = type(self)(self.fget, self.fset, fdel, self.__doc__)
971+
prop._name = self._name
972+
return prop
963973

964974
.. testcode::
965975
:hide:

Lib/test/test_property.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,16 @@ def __doc__(cls):
204204
return 'Second'
205205
self.assertEqual(A.__doc__, 'Second')
206206

207+
def test_property_set_name_incorrect_args(self):
208+
p = property()
209+
210+
for i in (0, 1, 3):
211+
with self.assertRaisesRegex(
212+
TypeError,
213+
fr'^__set_name__\(\) takes 2 positional arguments but {i} were given$'
214+
):
215+
p.__set_name__(*([0] * i))
216+
207217

208218
# Issue 5890: subclasses of property do not preserve method __doc__ strings
209219
class PropertySub(property):
@@ -299,6 +309,46 @@ def spam(self):
299309
self.assertEqual(Foo.spam.__doc__, "a new docstring")
300310

301311

312+
class _PropertyUnreachableAttribute:
313+
msg_format = None
314+
obj = None
315+
cls = None
316+
317+
def _format_exc_msg(self, msg):
318+
return self.msg_format.format(msg)
319+
320+
@classmethod
321+
def setUpClass(cls):
322+
cls.obj = cls.cls()
323+
324+
def test_get_property(self):
325+
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("unreadable attribute")):
326+
self.obj.foo
327+
328+
def test_set_property(self):
329+
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't set attribute")):
330+
self.obj.foo = None
331+
332+
def test_del_property(self):
333+
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't delete attribute")):
334+
del self.obj.foo
335+
336+
337+
class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase):
338+
msg_format = "^{} 'foo'$"
339+
340+
class cls:
341+
foo = property()
342+
343+
344+
class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase):
345+
msg_format = "^{}$"
346+
347+
class cls:
348+
pass
349+
350+
cls.foo = property()
351+
302352

303353
if __name__ == '__main__':
304354
unittest.main()

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1329,7 +1329,7 @@ def getx(self): return self.__x
13291329
def setx(self, value): self.__x = value
13301330
def delx(self): del self.__x
13311331
x = property(getx, setx, delx, "")
1332-
check(x, size('4Pi'))
1332+
check(x, size('5Pi'))
13331333
# PyCapsule
13341334
# XXX
13351335
# rangeiterator
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Improve the error message for failed writes/deletes to property objects.
2+
When possible, the attribute name is now shown. Patch provided by
3+
Yurii Karabas.

Objects/descrobject.c

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,6 +1490,7 @@ typedef struct {
14901490
PyObject *prop_set;
14911491
PyObject *prop_del;
14921492
PyObject *prop_doc;
1493+
PyObject *prop_name;
14931494
int getter_doc;
14941495
} propertyobject;
14951496

@@ -1535,10 +1536,33 @@ property_deleter(PyObject *self, PyObject *deleter)
15351536
}
15361537

15371538

1539+
PyDoc_STRVAR(set_name_doc,
1540+
"Method to set name of a property.");
1541+
1542+
static PyObject *
1543+
property_set_name(PyObject *self, PyObject *args) {
1544+
if (PyTuple_GET_SIZE(args) != 2) {
1545+
PyErr_Format(
1546+
PyExc_TypeError,
1547+
"__set_name__() takes 2 positional arguments but %d were given",
1548+
PyTuple_GET_SIZE(args));
1549+
return NULL;
1550+
}
1551+
1552+
propertyobject *prop = (propertyobject *)self;
1553+
PyObject *name = PyTuple_GET_ITEM(args, 1);
1554+
1555+
Py_XINCREF(name);
1556+
Py_XSETREF(prop->prop_name, name);
1557+
1558+
Py_RETURN_NONE;
1559+
}
1560+
15381561
static PyMethodDef property_methods[] = {
15391562
{"getter", property_getter, METH_O, getter_doc},
15401563
{"setter", property_setter, METH_O, setter_doc},
15411564
{"deleter", property_deleter, METH_O, deleter_doc},
1565+
{"__set_name__", property_set_name, METH_VARARGS, set_name_doc},
15421566
{0}
15431567
};
15441568

@@ -1553,6 +1577,7 @@ property_dealloc(PyObject *self)
15531577
Py_XDECREF(gs->prop_set);
15541578
Py_XDECREF(gs->prop_del);
15551579
Py_XDECREF(gs->prop_doc);
1580+
Py_XDECREF(gs->prop_name);
15561581
Py_TYPE(self)->tp_free(self);
15571582
}
15581583

@@ -1566,7 +1591,12 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
15661591

15671592
propertyobject *gs = (propertyobject *)self;
15681593
if (gs->prop_get == NULL) {
1569-
PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
1594+
if (gs->prop_name != NULL) {
1595+
PyErr_Format(PyExc_AttributeError, "unreadable attribute %R", gs->prop_name);
1596+
} else {
1597+
PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
1598+
}
1599+
15701600
return NULL;
15711601
}
15721602

@@ -1584,10 +1614,18 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
15841614
else
15851615
func = gs->prop_set;
15861616
if (func == NULL) {
1587-
PyErr_SetString(PyExc_AttributeError,
1617+
if (gs->prop_name != NULL) {
1618+
PyErr_Format(PyExc_AttributeError,
15881619
value == NULL ?
1589-
"can't delete attribute" :
1590-
"can't set attribute");
1620+
"can't delete attribute %R" :
1621+
"can't set attribute %R",
1622+
gs->prop_name);
1623+
} else {
1624+
PyErr_SetString(PyExc_AttributeError,
1625+
value == NULL ?
1626+
"can't delete attribute" :
1627+
"can't set attribute");
1628+
}
15911629
return -1;
15921630
}
15931631
if (value == NULL)
@@ -1634,6 +1672,9 @@ property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *del)
16341672
Py_DECREF(type);
16351673
if (new == NULL)
16361674
return NULL;
1675+
1676+
Py_XINCREF(pold->prop_name);
1677+
Py_XSETREF(((propertyobject *) new)->prop_name, pold->prop_name);
16371678
return new;
16381679
}
16391680

@@ -1695,6 +1736,8 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
16951736
Py_XSETREF(self->prop_set, fset);
16961737
Py_XSETREF(self->prop_del, fdel);
16971738
Py_XSETREF(self->prop_doc, doc);
1739+
Py_XSETREF(self->prop_name, NULL);
1740+
16981741
self->getter_doc = 0;
16991742

17001743
/* if no docstring given and the getter has one, use that one */
@@ -1769,6 +1812,7 @@ property_traverse(PyObject *self, visitproc visit, void *arg)
17691812
Py_VISIT(pp->prop_set);
17701813
Py_VISIT(pp->prop_del);
17711814
Py_VISIT(pp->prop_doc);
1815+
Py_VISIT(pp->prop_name);
17721816
return 0;
17731817
}
17741818

0 commit comments

Comments
 (0)