Skip to content

Commit 8ec1251

Browse files
committed
gh-101860: Expose __name__ on property
Useful for introspection and consistent with functions and other descriptors.
1 parent 4379244 commit 8ec1251

File tree

8 files changed

+60
-22
lines changed

8 files changed

+60
-22
lines changed

Doc/howto/descriptor.rst

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,47 +1004,50 @@ 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 = ''
1007+
self.__name__ = None
10081008

10091009
def __set_name__(self, owner, name):
1010-
self._name = name
1010+
self.__name__ = name
10111011

10121012
def __get__(self, obj, objtype=None):
10131013
if obj is None:
10141014
return self
10151015
if self.fget is None:
10161016
raise AttributeError(
1017-
f'property {self._name!r} of {type(obj).__name__!r} object has no getter'
1017+
f'property {self.__name__!r} of {type(obj).__name__!r} '
1018+
'object has no getter'
10181019
)
10191020
return self.fget(obj)
10201021

10211022
def __set__(self, obj, value):
10221023
if self.fset is None:
10231024
raise AttributeError(
1024-
f'property {self._name!r} of {type(obj).__name__!r} object has no setter'
1025+
f'property {self.__name__!r} of {type(obj).__name__!r} '
1026+
'object has no setter'
10251027
)
10261028
self.fset(obj, value)
10271029

10281030
def __delete__(self, obj):
10291031
if self.fdel is None:
10301032
raise AttributeError(
1031-
f'property {self._name!r} of {type(obj).__name__!r} object has no deleter'
1033+
f'property {self.__name__!r} of {type(obj).__name__!r} '
1034+
'object has no deleter'
10321035
)
10331036
self.fdel(obj)
10341037

10351038
def getter(self, fget):
10361039
prop = type(self)(fget, self.fset, self.fdel, self.__doc__)
1037-
prop._name = self._name
1040+
prop.__name__ = self.__name__
10381041
return prop
10391042

10401043
def setter(self, fset):
10411044
prop = type(self)(self.fget, fset, self.fdel, self.__doc__)
1042-
prop._name = self._name
1045+
prop.__name__ = self.__name__
10431046
return prop
10441047

10451048
def deleter(self, fdel):
10461049
prop = type(self)(self.fget, self.fset, fdel, self.__doc__)
1047-
prop._name = self._name
1050+
prop.__name__ = self.__name__
10481051
return prop
10491052

10501053
.. 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 = 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 = 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_inspect/inspect_fodder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ class FesteringGob(MalodorousPervert, ParrotDroppings):
6868
def abuse(self, a, b, c):
6969
pass
7070

71-
@property
72-
def contradiction(self):
71+
def _getter(self):
7372
pass
73+
contradiction = property(_getter)
7474

7575
async def lobbest(grenade):
7676
pass

Lib/test/test_property.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,26 @@ def test_refleaks_in___init__(self):
183183
fake_prop.__init__('fget', 'fset', 'fdel', 'doc')
184184
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
185185

186+
def test_property_name(self):
187+
def getter(self):
188+
return 42
189+
190+
class A:
191+
@property
192+
def foo(self):
193+
return 1
194+
195+
bar = property(getter)
196+
197+
self.assertEqual(A.foo.__name__, 'foo')
198+
self.assertEqual(A.bar.__name__, 'bar')
199+
200+
A.baz = property(getter)
201+
self.assertIsNone(A.baz.__name__)
202+
A.baz.__name__ = 'mybaz'
203+
self.assertEqual(A.baz.__name__, 'mybaz')
204+
self.assertEqual(A.bar.__name__, 'bar') # not affected
205+
186206
def test_property_set_name_incorrect_args(self):
187207
p = property()
188208

Lib/test/test_pydoc/test_pydoc.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,17 @@ def test_importfile(self):
11621162
self.assertEqual(loaded_pydoc.__spec__, pydoc.__spec__)
11631163

11641164

1165+
class Rect:
1166+
@property
1167+
def area(self):
1168+
'''Area of the rect'''
1169+
return self.w * self.h
1170+
1171+
1172+
class Square(Rect):
1173+
area = property(lambda self: self.side**2)
1174+
1175+
11651176
class TestDescriptions(unittest.TestCase):
11661177

11671178
def test_module(self):
@@ -1550,13 +1561,13 @@ def test_namedtuple_field_descriptor(self):
15501561

15511562
@requires_docstrings
15521563
def test_property(self):
1553-
class Rect:
1554-
@property
1555-
def area(self):
1556-
'''Area of the rect'''
1557-
return self.w * self.h
1558-
15591564
self.assertEqual(self._get_summary_lines(Rect.area), """\
1565+
area
1566+
Area of the rect
1567+
""")
1568+
# inherits the docstring from Rect.area
1569+
self.assertEqual(self._get_summary_lines(Square.area), """\
1570+
area
15601571
Area of the rect
15611572
""")
15621573
self.assertIn("""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose ``__name__`` attribute on property.

Objects/descrobject.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,10 @@ class property(object):
15191519
self.__doc__ = doc
15201520
except AttributeError: # read-only or dict-less class
15211521
pass
1522+
self.__name__ = None
1523+
1524+
def __set_name__(self, owner, name):
1525+
self.__name__ = name
15221526
15231527
def __get__(self, inst, type=None):
15241528
if inst is None:
@@ -1547,6 +1551,7 @@ static PyMemberDef property_members[] = {
15471551
{"fset", _Py_T_OBJECT, offsetof(propertyobject, prop_set), Py_READONLY},
15481552
{"fdel", _Py_T_OBJECT, offsetof(propertyobject, prop_del), Py_READONLY},
15491553
{"__doc__", _Py_T_OBJECT, offsetof(propertyobject, prop_doc), 0},
1554+
{"__name__", _Py_T_OBJECT, offsetof(propertyobject, prop_name), 0},
15501555
{0}
15511556
};
15521557

0 commit comments

Comments
 (0)