diff --git a/Lib/pydoc.py b/Lib/pydoc.py --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -372,16 +372,17 @@ class Doc: def document(self, object, name=None, *args): """Generate documentation for an object.""" args = (object, name) + args # 'try' clause is to attempt to handle the possibility that inspect # identifies something in a way that pydoc itself has issues handling; # think 'super' and how it is a descriptor (which raises the exception # by lacking a __name__ attribute) and an instance. if inspect.isgetsetdescriptor(object): return self.docdata(*args) + if inspect.isdatadescriptor(object): return self.docdata(*args) if inspect.ismemberdescriptor(object): return self.docdata(*args) try: if inspect.ismodule(object): return self.docmodule(*args) if inspect.isclass(object): return self.docclass(*args) if inspect.isroutine(object): return self.docroutine(*args) except AttributeError: pass if isinstance(object, property): return self.docproperty(*args) @@ -1623,16 +1624,17 @@ def render_doc(thing, title='Python Libr elif module and module is not object: desc += ' in module ' + module.__name__ if not (inspect.ismodule(object) or inspect.isclass(object) or inspect.isroutine(object) or inspect.isgetsetdescriptor(object) or inspect.ismemberdescriptor(object) or + inspect.isdatadescriptor(object) or isinstance(object, property)): # If the passed object is a piece of data or an instance, # document its available methods instead of its value. object = type(object) desc += ' object' return title % desc + '\n\n' + renderer.document(object, name) def doc(thing, title='Python Library Documentation: %s', forceload=0, diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -1052,24 +1052,74 @@ class PydocWithMetaClasses(unittest.Test # Issue #23008: pydoc enum.{,Int}Enum failed # because bool(enum.Enum) is False. with captured_stdout() as help_io: pydoc.help('enum.Enum') helptext = help_io.getvalue() self.assertIn('class Enum', helptext) +class Property: + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + if doc is None and fget is not None: + doc = fget.__doc__ + self.__doc__ = doc + + def __get__(self, obj, objtype=None): + if obj is None: + return self + if self.fget is None: + raise AttributeError("unreadable attribute") + return self.fget(obj) + + def __set__(self, obj, value): + if self.fset is None: + raise AttributeError("can't set attribute") + self.fset(obj, value) + + +class PydocWithDescriptors(unittest.TestCase): + + def assertDocument(self, object, expected): + self.assertEqual(pydoc.TextDoc().document(object).strip(), expected) + + def test_data_descriptor(self): + class Spam: + y = Property(lambda self: 7, doc='more helpful text') + + def _z_getter(self): + """spam eggs""" + z = Property(fget=_z_getter) + + self.assertDocument(Spam.y, 'more helpful text') + self.assertDocument(Spam.z, 'spam eggs') + + @unittest.skip('needs #24766') + def test_property_subclass(self): + class PropertySub(property): + pass + + class Spam: + y = PropertySub(lambda self: 7, doc='more helpful text') + + self.assertDocument(Spam.y, 'more helpful text') + + @reap_threads def test_main(): try: test.support.run_unittest(PydocDocTest, PydocImportTest, TestDescriptions, PydocServerTest, PydocUrlHandlerTest, TestHelper, PydocWithMetaClasses, + PydocWithDescriptors, ) finally: reap_children() if __name__ == "__main__": test_main()