From 05db108e7674059f25e3bd835b112853c160f3de Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Mon, 13 Feb 2023 11:21:38 -0500 Subject: [PATCH] gh-101860: Expose __name__ on property Useful for introspection and consistent with functions and other descriptors. --- Doc/howto/descriptor.rst | 16 +++++++-------- Lib/inspect.py | 5 ++--- Lib/pydoc.py | 5 ++--- Lib/test/test_property.py | 20 +++++++++++++++++++ Lib/test/test_pydoc.py | 1 + ...-02-13-11-36-50.gh-issue-101860.CKCMbC.rst | 1 + Objects/descrobject.c | 5 +++++ 7 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index 74710d9b3fc2edc..d6e21a18aeb36ab 100644 --- a/Doc/howto/descriptor.rst +++ b/Doc/howto/descriptor.rst @@ -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:: diff --git a/Lib/inspect.py b/Lib/inspect.py index 8bb3a375735af6a..82d35cdab0fd146 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -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): diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 0a693f45230c93d..baecb004167ceff 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -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): diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index d4bdf50c0192ae6..b3d038d56c60e03 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -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() diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index cefc71cb5a7f54d..aa03aeb2e09b2d2 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -1265,6 +1265,7 @@ def area(self): return self.w * self.h self.assertEqual(self._get_summary_lines(Rect.area), """\ +area Area of the rect """) self.assertIn(""" diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst b/Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst new file mode 100644 index 000000000000000..5a2743534669737 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst @@ -0,0 +1 @@ +Expose ``__name__`` attribute on property. diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 334be75e8df9dfb..4e51007881d378a 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -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: @@ -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} };