Skip to content

Commit c20e9a7

Browse files
committed
Fallback to fget.__name__ if name is not set
Raise AttributeError if no fget or it doesn't have `__name__`.
1 parent 5dfa4f4 commit c20e9a7

File tree

5 files changed

+118
-25
lines changed

5 files changed

+118
-25
lines changed

Doc/howto/descriptor.rst

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,10 +1004,18 @@ here is a pure Python equivalent:
10041004
if doc is None and fget is not None:
10051005
doc = fget.__doc__
10061006
self.__doc__ = doc
1007-
self.__name__ = None
1007+
self._name = None
10081008

10091009
def __set_name__(self, owner, name):
1010-
self.__name__ = name
1010+
self._name = name
1011+
1012+
@property
1013+
def __name__(self):
1014+
return self._name if self._name is not None else self.fget.__name__
1015+
1016+
@__name__.setter
1017+
def __name__(self, value):
1018+
self._name = value
10111019

10121020
def __get__(self, obj, objtype=None):
10131021
if obj is None:
@@ -1037,17 +1045,17 @@ here is a pure Python equivalent:
10371045

10381046
def getter(self, fget):
10391047
prop = type(self)(fget, self.fset, self.fdel, self.__doc__)
1040-
prop.__name__ = self.__name__
1048+
prop._name = self._name
10411049
return prop
10421050

10431051
def setter(self, fset):
10441052
prop = type(self)(self.fget, fset, self.fdel, self.__doc__)
1045-
prop.__name__ = self.__name__
1053+
prop._name = self._name
10461054
return prop
10471055

10481056
def deleter(self, fdel):
10491057
prop = type(self)(self.fget, self.fset, fdel, self.__doc__)
1050-
prop.__name__ = self.__name__
1058+
prop._name = self._name
10511059
return prop
10521060

10531061
.. testcode::

Lib/inspect.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -834,9 +834,8 @@ def _finddoc(obj):
834834
cls = self.__class__
835835
# Should be tested before isdatadescriptor().
836836
elif isinstance(obj, property):
837-
func = obj.fget
838-
name = obj.__name__ or func.__name__
839-
cls = _findclass(func)
837+
name = obj.__name__
838+
cls = _findclass(obj.fget)
840839
if cls is None or getattr(cls, name) is not obj:
841840
return None
842841
elif ismethoddescriptor(obj) or isdatadescriptor(obj):

Lib/pydoc.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,8 @@ def _finddoc(obj):
127127
cls = self.__class__
128128
# Should be tested before isdatadescriptor().
129129
elif isinstance(obj, property):
130-
func = obj.fget
131-
name = obj.__name__ or func.__name__
132-
cls = _findclass(func)
130+
name = obj.__name__
131+
cls = _findclass(obj.fget)
133132
if cls is None or getattr(cls, name) is not obj:
134133
return None
135134
elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj):

Lib/test/test_property.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,21 +205,54 @@ def test_property_name(self):
205205
def getter(self):
206206
return 42
207207

208+
def setter(self, value):
209+
pass
210+
208211
class A:
209212
@property
210213
def foo(self):
211214
return 1
212215

216+
@foo.setter
217+
def oof(self, value):
218+
pass
219+
213220
bar = property(getter)
221+
baz = property(None, setter)
214222

215223
self.assertEqual(A.foo.__name__, 'foo')
224+
self.assertEqual(A.oof.__name__, 'oof')
216225
self.assertEqual(A.bar.__name__, 'bar')
226+
self.assertEqual(A.baz.__name__, 'baz')
217227

218-
A.baz = property(getter)
219-
self.assertIsNone(A.baz.__name__)
220-
A.baz.__name__ = 'mybaz'
221-
self.assertEqual(A.baz.__name__, 'mybaz')
228+
A.quux = property(getter)
229+
self.assertEqual(A.quux.__name__, 'getter')
230+
A.quux.__name__ = 'myquux'
231+
self.assertEqual(A.quux.__name__, 'myquux')
222232
self.assertEqual(A.bar.__name__, 'bar') # not affected
233+
A.quux.__name__ = None
234+
self.assertIsNone(A.quux.__name__)
235+
236+
with self.assertRaisesRegex(
237+
AttributeError, "'property' object has no attribute '__name__'"
238+
):
239+
property(None, setter).__name__
240+
241+
with self.assertRaisesRegex(
242+
AttributeError, "'property' object has no attribute '__name__'"
243+
):
244+
property(1).__name__
245+
246+
class Err:
247+
def __getattr__(self, attr):
248+
raise RuntimeError('fail')
249+
250+
p = property(Err())
251+
with self.assertRaisesRegex(RuntimeError, 'fail'):
252+
p.__name__
253+
254+
p.__name__ = 'not_fail'
255+
self.assertEqual(p.__name__, 'not_fail')
223256

224257
def test_property_set_name_incorrect_args(self):
225258
p = property()

Objects/descrobject.c

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,26 +1519,34 @@ class property(object):
15191519
self.__doc__ = doc
15201520
except AttributeError: # read-only or dict-less class
15211521
pass
1522-
self.__name__ = None
1522+
self.__name = None
15231523
15241524
def __set_name__(self, owner, name):
1525-
self.__name__ = name
1525+
self.__name = name
1526+
1527+
@property
1528+
def __name__(self):
1529+
return self.__name if self.__name is not None else self.fget.__name__
1530+
1531+
@__name__.setter
1532+
def __name__(self, value):
1533+
self.__name = value
15261534
15271535
def __get__(self, inst, type=None):
15281536
if inst is None:
15291537
return self
15301538
if self.__get is None:
1531-
raise AttributeError, "property has no getter"
1539+
raise AttributeError("property has no getter")
15321540
return self.__get(inst)
15331541
15341542
def __set__(self, inst, value):
15351543
if self.__set is None:
1536-
raise AttributeError, "property has no setter"
1544+
raise AttributeError("property has no setter")
15371545
return self.__set(inst, value)
15381546
15391547
def __delete__(self, inst):
15401548
if self.__del is None:
1541-
raise AttributeError, "property has no deleter"
1549+
raise AttributeError("property has no deleter")
15421550
return self.__del(inst)
15431551
15441552
*/
@@ -1551,7 +1559,6 @@ static PyMemberDef property_members[] = {
15511559
{"fset", _Py_T_OBJECT, offsetof(propertyobject, prop_set), Py_READONLY},
15521560
{"fdel", _Py_T_OBJECT, offsetof(propertyobject, prop_del), Py_READONLY},
15531561
{"__doc__", _Py_T_OBJECT, offsetof(propertyobject, prop_doc), 0},
1554-
{"__name__", _Py_T_OBJECT, offsetof(propertyobject, prop_name), 0},
15551562
{0}
15561563
};
15571564

@@ -1633,6 +1640,20 @@ property_dealloc(PyObject *self)
16331640
Py_TYPE(self)->tp_free(self);
16341641
}
16351642

1643+
static int
1644+
property_name(propertyobject *prop, PyObject **name)
1645+
{
1646+
if (prop->prop_name != NULL) {
1647+
*name = Py_NewRef(prop->prop_name);
1648+
return 1;
1649+
}
1650+
if (prop->prop_get == NULL) {
1651+
*name = NULL;
1652+
return 0;
1653+
}
1654+
return PyObject_GetOptionalAttr(prop->prop_get, &_Py_ID(__name__), name);
1655+
}
1656+
16361657
static PyObject *
16371658
property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
16381659
{
@@ -1642,11 +1663,15 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
16421663

16431664
propertyobject *gs = (propertyobject *)self;
16441665
if (gs->prop_get == NULL) {
1666+
PyObject *propname;
1667+
if (property_name(gs, &propname) < 0) {
1668+
return NULL;
1669+
}
16451670
PyObject *qualname = PyType_GetQualName(Py_TYPE(obj));
1646-
if (gs->prop_name != NULL && qualname != NULL) {
1671+
if (propname != NULL && qualname != NULL) {
16471672
PyErr_Format(PyExc_AttributeError,
16481673
"property %R of %R object has no getter",
1649-
gs->prop_name,
1674+
propname,
16501675
qualname);
16511676
}
16521677
else if (qualname != NULL) {
@@ -1657,6 +1682,7 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
16571682
PyErr_SetString(PyExc_AttributeError,
16581683
"property has no getter");
16591684
}
1685+
Py_XDECREF(propname);
16601686
Py_XDECREF(qualname);
16611687
return NULL;
16621688
}
@@ -1678,16 +1704,20 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
16781704
}
16791705

16801706
if (func == NULL) {
1707+
PyObject *propname;
1708+
if (property_name(gs, &propname) < 0) {
1709+
return -1;
1710+
}
16811711
PyObject *qualname = NULL;
16821712
if (obj != NULL) {
16831713
qualname = PyType_GetQualName(Py_TYPE(obj));
16841714
}
1685-
if (gs->prop_name != NULL && qualname != NULL) {
1715+
if (propname != NULL && qualname != NULL) {
16861716
PyErr_Format(PyExc_AttributeError,
16871717
value == NULL ?
16881718
"property %R of %R object has no deleter" :
16891719
"property %R of %R object has no setter",
1690-
gs->prop_name,
1720+
propname,
16911721
qualname);
16921722
}
16931723
else if (qualname != NULL) {
@@ -1703,6 +1733,7 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
17031733
"property has no deleter" :
17041734
"property has no setter");
17051735
}
1736+
Py_XDECREF(propname);
17061737
Py_XDECREF(qualname);
17071738
return -1;
17081739
}
@@ -1888,6 +1919,28 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
18881919
return 0;
18891920
}
18901921

1922+
static PyObject *
1923+
property_get__name__(propertyobject *prop, void *Py_UNUSED(ignored))
1924+
{
1925+
PyObject *name;
1926+
if (property_name(prop, &name) < 0) {
1927+
return NULL;
1928+
}
1929+
if (name == NULL) {
1930+
PyErr_SetString(PyExc_AttributeError,
1931+
"'property' object has no attribute '__name__'");
1932+
}
1933+
return name;
1934+
}
1935+
1936+
static int
1937+
property_set__name__(propertyobject *prop, PyObject *value,
1938+
void *Py_UNUSED(ignored))
1939+
{
1940+
Py_XSETREF(prop->prop_name, Py_XNewRef(value));
1941+
return 0;
1942+
}
1943+
18911944
static PyObject *
18921945
property_get___isabstractmethod__(propertyobject *prop, void *closure)
18931946
{
@@ -1918,6 +1971,7 @@ property_get___isabstractmethod__(propertyobject *prop, void *closure)
19181971
}
19191972

19201973
static PyGetSetDef property_getsetlist[] = {
1974+
{"__name__", (getter)property_get__name__, (setter)property_set__name__},
19211975
{"__isabstractmethod__",
19221976
(getter)property_get___isabstractmethod__, NULL,
19231977
NULL,

0 commit comments

Comments
 (0)