[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