[Python-checkins] cpython: Issue #15582: inspect.getdoc() now follows inheritance chains.

serhiy.storchaka python-checkins at python.org
Fri Apr 3 21:39:32 CEST 2015


https://hg.python.org/cpython/rev/157b7055bb9d
changeset:   95421:157b7055bb9d
user:        Serhiy Storchaka <storchaka at gmail.com>
date:        Fri Apr 03 22:38:53 2015 +0300
summary:
  Issue #15582: inspect.getdoc() now follows inheritance chains.

files:
  Doc/library/inspect.rst    |   3 +
  Lib/inspect.py             |  73 ++++++++++++++++++++++++++
  Lib/test/inspect_fodder.py |  14 ++++-
  Lib/test/test_inspect.py   |  23 +++++++-
  Misc/NEWS                  |   2 +
  5 files changed, 112 insertions(+), 3 deletions(-)


diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -356,6 +356,9 @@
 .. 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)
diff --git a/Lib/inspect.py b/Lib/inspect.py
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -468,6 +468,74 @@
     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 @@
         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)
diff --git a/Lib/test/inspect_fodder.py b/Lib/test/inspect_fodder.py
--- a/Lib/test/inspect_fodder.py
+++ b/Lib/test/inspect_fodder.py
@@ -45,9 +45,16 @@
             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 @@
     pass
 
 class FesteringGob(MalodorousPervert, ParrotDroppings):
-    pass
+    def abuse(self, a, b, c):
+        pass
+    def contradiction(self):
+        pass
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -292,6 +292,27 @@
         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 @@
 
     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)
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -16,6 +16,8 @@
 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

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list