Issue #15582: inspect.getdoc() now follows inheritance chains.
This commit is contained in:
parent
41525e31a5
commit
5cf2b7253d
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -45,8 +45,15 @@ class StupidGit:
|
|||
self.ex = sys.exc_info()
|
||||
self.tr = inspect.trace()
|
||||
|
||||
def contradiction(self):
|
||||
'The automatic gainsaying.'
|
||||
pass
|
||||
|
||||
# line 48
|
||||
class MalodorousPervert(StupidGit):
|
||||
def abuse(self, a, b, c):
|
||||
pass
|
||||
def contradiction(self):
|
||||
pass
|
||||
|
||||
Tit = MalodorousPervert
|
||||
|
@ -55,4 +62,7 @@ class ParrotDroppings:
|
|||
pass
|
||||
|
||||
class FesteringGob(MalodorousPervert, ParrotDroppings):
|
||||
def abuse(self, a, b, c):
|
||||
pass
|
||||
def contradiction(self):
|
||||
pass
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue