Skip to content

Commit

Permalink
pythongh-101860: Expose __name__ on property
Browse files Browse the repository at this point in the history
Useful for introspection and consistent with functions and other
descriptors.
  • Loading branch information
eltoder committed Feb 13, 2023
1 parent 6ef6915 commit 9c956c7
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 22 deletions.
16 changes: 8 additions & 8 deletions Doc/howto/descriptor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1000,41 +1000,41 @@ here is a pure Python equivalent:
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
self._name = ''
self.__name__ = None

def __set_name__(self, owner, name):
self._name = name
self.__name__ = name

def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError(f"property '{self._name}' has no getter")
raise AttributeError(f"property '{self.__name__}' has no getter")
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError(f"property '{self._name}' has no setter")
raise AttributeError(f"property '{self.__name__}' has no setter")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError(f"property '{self._name}' has no deleter")
raise AttributeError(f"property '{self.__name__}' has no deleter")
self.fdel(obj)

def getter(self, fget):
prop = type(self)(fget, self.fset, self.fdel, self.__doc__)
prop._name = self._name
prop.__name__ = self.__name__
return prop

def setter(self, fset):
prop = type(self)(self.fget, fset, self.fdel, self.__doc__)
prop._name = self._name
prop.__name__ = self.__name__
return prop

def deleter(self, fdel):
prop = type(self)(self.fget, self.fset, fdel, self.__doc__)
prop._name = self._name
prop.__name__ = self.__name__
return prop

.. testcode::
Expand Down
5 changes: 2 additions & 3 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,9 +825,8 @@ def _finddoc(obj):
cls = self.__class__
# Should be tested before isdatadescriptor().
elif isinstance(obj, property):
func = obj.fget
name = func.__name__
cls = _findclass(func)
name = obj.__name__
cls = _findclass(obj.fget)
if cls is None or getattr(cls, name) is not obj:
return None
elif ismethoddescriptor(obj) or isdatadescriptor(obj):
Expand Down
5 changes: 2 additions & 3 deletions Lib/pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,8 @@ def _finddoc(obj):
cls = self.__class__
# Should be tested before isdatadescriptor().
elif isinstance(obj, property):
func = obj.fget
name = func.__name__
cls = _findclass(func)
name = obj.__name__
cls = _findclass(obj.fget)
if cls is None or getattr(cls, name) is not obj:
return None
elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj):
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/inspect_fodder.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ class FesteringGob(MalodorousPervert, ParrotDroppings):
def abuse(self, a, b, c):
pass

@property
def contradiction(self):
def _getter(self):
pass
contradiction = property(_getter)

async def lobbest(grenade):
pass
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,26 @@ def __doc__(cls):
return 'Second'
self.assertEqual(A.__doc__, 'Second')

def test_property_name(self):
def getter(self):
return 42

class A:
@property
def foo(self):
return 1

bar = property(getter)

self.assertEqual(A.foo.__name__, 'foo')
self.assertEqual(A.bar.__name__, 'bar')

A.baz = property(getter)
self.assertIsNone(A.baz.__name__)
A.baz.__name__ = 'mybaz'
self.assertEqual(A.baz.__name__, 'mybaz')
self.assertEqual(A.bar.__name__, 'bar') # not affected

def test_property_set_name_incorrect_args(self):
p = property()

Expand Down
23 changes: 17 additions & 6 deletions Lib/test/test_pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,17 @@ def test_importfile(self):
self.assertEqual(loaded_pydoc.__spec__, pydoc.__spec__)


class Rect:
@property
def area(self):
'''Area of the rect'''
return self.w * self.h


class Square(Rect):
area = property(lambda self: self.side**2)


class TestDescriptions(unittest.TestCase):

def test_module(self):
Expand Down Expand Up @@ -1258,13 +1269,13 @@ def test_namedtuple_field_descriptor(self):

@requires_docstrings
def test_property(self):
class Rect:
@property
def area(self):
'''Area of the rect'''
return self.w * self.h

self.assertEqual(self._get_summary_lines(Rect.area), """\
area
Area of the rect
""")
# inherits the docstring from Rect.area
self.assertEqual(self._get_summary_lines(Square.area), """\
area
Area of the rect
""")
self.assertIn("""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose ``__name__`` attribute on property.
5 changes: 5 additions & 0 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,10 @@ class property(object):
self.__set = fset
self.__del = fdel
self.__doc__ = doc
self.__name__ = None
def __set_name__(self, owner, name):
self.__name__ = name
def __get__(self, inst, type=None):
if inst is None:
Expand Down Expand Up @@ -1508,6 +1512,7 @@ static PyMemberDef property_members[] = {
{"fset", T_OBJECT, offsetof(propertyobject, prop_set), READONLY},
{"fdel", T_OBJECT, offsetof(propertyobject, prop_del), READONLY},
{"__doc__", T_OBJECT, offsetof(propertyobject, prop_doc), 0},
{"__name__", T_OBJECT, offsetof(propertyobject, prop_name), 0},
{0}
};

Expand Down

0 comments on commit 9c956c7

Please sign in to comment.