]> granicus.if.org Git - python/commitdiff
Issue #15582: inspect.getdoc() now follows inheritance chains.
authorSerhiy Storchaka <storchaka@gmail.com>
Fri, 3 Apr 2015 19:38:53 +0000 (22:38 +0300)
committerSerhiy Storchaka <storchaka@gmail.com>
Fri, 3 Apr 2015 19:38:53 +0000 (22:38 +0300)
Doc/library/inspect.rst
Lib/inspect.py
Lib/test/inspect_fodder.py
Lib/test/test_inspect.py
Misc/NEWS

index 3d2132fd86426383a8fa6aad63c62a73e436c0ac..471200fa7870dd5328d81c17b7ecb639ebf3103a 100644 (file)
@@ -356,6 +356,9 @@ Retrieving source code
 .. function:: getdoc(object)
 
    Get the documentation string for an object, cleaned up with :func:`cleandoc`.
+   If the documentation string for an object is not provided and the object is
+   a class, a method, a property or a descriptor, retrieve the documentation
+   string from the inheritance hierarchy.
 
 
 .. function:: getcomments(object)
index 98d665dd45f7fe76dd3ae6d840b503a3e2674f77..81b1ce87098bde19cc335b91cecaf5582a0892fe 100644 (file)
@@ -468,6 +468,74 @@ def indentsize(line):
     expline = line.expandtabs()
     return len(expline) - len(expline.lstrip())
 
+def _findclass(func):
+    cls = sys.modules.get(func.__module__)
+    if cls is None:
+        return None
+    for name in func.__qualname__.split('.')[:-1]:
+        cls = getattr(cls, name)
+    if not isclass(cls):
+        return None
+    return cls
+
+def _finddoc(obj):
+    if isclass(obj):
+        for base in obj.__mro__:
+            if base is not object:
+                try:
+                    doc = base.__doc__
+                except AttributeError:
+                    continue
+                if doc is not None:
+                    return doc
+        return None
+
+    if ismethod(obj):
+        name = obj.__func__.__name__
+        self = obj.__self__
+        if (isclass(self) and
+            getattr(getattr(self, name, None), '__func__') is obj.__func__):
+            # classmethod
+            cls = self
+        else:
+            cls = self.__class__
+    elif isfunction(obj):
+        name = obj.__name__
+        cls = _findclass(obj)
+        if cls is None or getattr(cls, name) is not obj:
+            return None
+    elif isbuiltin(obj):
+        name = obj.__name__
+        self = obj.__self__
+        if (isclass(self) and
+            self.__qualname__ + '.' + name == obj.__qualname__):
+            # classmethod
+            cls = self
+        else:
+            cls = self.__class__
+    elif ismethoddescriptor(obj) or isdatadescriptor(obj):
+        name = obj.__name__
+        cls = obj.__objclass__
+        if getattr(cls, name) is not obj:
+            return None
+    elif isinstance(obj, property):
+        func = f.fget
+        name = func.__name__
+        cls = _findclass(func)
+        if cls is None or getattr(cls, name) is not obj:
+            return None
+    else:
+        return None
+
+    for base in cls.__mro__:
+        try:
+            doc = getattr(base, name).__doc__
+        except AttributeError:
+            continue
+        if doc is not None:
+            return doc
+    return None
+
 def getdoc(object):
     """Get the documentation string for an object.
 
@@ -478,6 +546,11 @@ def getdoc(object):
         doc = object.__doc__
     except AttributeError:
         return None
+    if doc is None:
+        try:
+            doc = _finddoc(object)
+        except (AttributeError, TypeError):
+            return None
     if not isinstance(doc, str):
         return None
     return cleandoc(doc)
index 0c1d8103e9d0cb0d97099b05a08ae64fe5d003b2..6f0cad942d35858f628191a7e8ef3945b06c6e3a 100644 (file)
@@ -45,9 +45,16 @@ class StupidGit:
             self.ex = sys.exc_info()
             self.tr = inspect.trace()
 
+    def contradiction(self):
+        'The automatic gainsaying.'
+        pass
+
 # line 48
 class MalodorousPervert(StupidGit):
-    pass
+    def abuse(self, a, b, c):
+        pass
+    def contradiction(self):
+        pass
 
 Tit = MalodorousPervert
 
@@ -55,4 +62,7 @@ class ParrotDroppings:
     pass
 
 class FesteringGob(MalodorousPervert, ParrotDroppings):
-    pass
+    def abuse(self, a, b, c):
+        pass
+    def contradiction(self):
+        pass
index 401e6cf4d4916c8604fe5f0da3529a44bde48395..2f7e58207bc4e013f6724a47782ce390950995d6 100644 (file)
@@ -292,6 +292,27 @@ class TestRetrievingSourceCode(GetSourceBase):
         self.assertEqual(inspect.getdoc(git.abuse),
                          'Another\n\ndocstring\n\ncontaining\n\ntabs')
 
+    @unittest.skipIf(sys.flags.optimize >= 2,
+                     "Docstrings are omitted with -O2 and above")
+    def test_getdoc_inherited(self):
+        self.assertEqual(inspect.getdoc(mod.FesteringGob),
+                         'A longer,\n\nindented\n\ndocstring.')
+        self.assertEqual(inspect.getdoc(mod.FesteringGob.abuse),
+                         'Another\n\ndocstring\n\ncontaining\n\ntabs')
+        self.assertEqual(inspect.getdoc(mod.FesteringGob().abuse),
+                         'Another\n\ndocstring\n\ncontaining\n\ntabs')
+        self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction),
+                         'The automatic gainsaying.')
+
+    @unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings")
+    def test_finddoc(self):
+        finddoc = inspect._finddoc
+        self.assertEqual(finddoc(int), int.__doc__)
+        self.assertEqual(finddoc(int.to_bytes), int.to_bytes.__doc__)
+        self.assertEqual(finddoc(int().to_bytes), int.to_bytes.__doc__)
+        self.assertEqual(finddoc(int.from_bytes), int.from_bytes.__doc__)
+        self.assertEqual(finddoc(int.real), int.real.__doc__)
+
     def test_cleandoc(self):
         self.assertEqual(inspect.cleandoc('An\n    indented\n    docstring.'),
                          'An\nindented\ndocstring.')
@@ -316,7 +337,7 @@ class TestRetrievingSourceCode(GetSourceBase):
 
     def test_getsource(self):
         self.assertSourceEqual(git.abuse, 29, 39)
-        self.assertSourceEqual(mod.StupidGit, 21, 46)
+        self.assertSourceEqual(mod.StupidGit, 21, 50)
 
     def test_getsourcefile(self):
         self.assertEqual(normcase(inspect.getsourcefile(mod.spam)), modfile)
index b2306749b48d334d81a0b60c7996e7d6505a8785..199473c144a52fd78f38043a513fab6ecb53e012 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -16,6 +16,8 @@ Core and Builtins
 Library
 -------
 
+- Issue #15582: inspect.getdoc() now follows inheritance chains.
+
 - Issue #2175: SAX parsers now support a character stream of InputSource object.
 
 - Issue #16840: Tkinter now supports 64-bit integers added in Tcl 8.4 and