Skip to content

Commit 7906f4d

Browse files
gh-132686: Add parameters inherit_class_doc and fallback_to_class_doc for inspect.getdoc() (GH-132691)
1 parent c744ccb commit 7906f4d

File tree

6 files changed

+79
-101
lines changed

6 files changed

+79
-101
lines changed

Doc/library/inspect.rst

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -619,17 +619,26 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
619619
Retrieving source code
620620
----------------------
621621

622-
.. function:: getdoc(object)
622+
.. function:: getdoc(object, *, inherit_class_doc=True, fallback_to_class_doc=True)
623623

624624
Get the documentation string for an object, cleaned up with :func:`cleandoc`.
625-
If the documentation string for an object is not provided and the object is
626-
a class, a method, a property or a descriptor, retrieve the documentation
627-
string from the inheritance hierarchy.
625+
If the documentation string for an object is not provided:
626+
627+
* if the object is a class and *inherit_class_doc* is true (by default),
628+
retrieve the documentation string from the inheritance hierarchy;
629+
* if the object is a method, a property or a descriptor, retrieve
630+
the documentation string from the inheritance hierarchy;
631+
* otherwise, if *fallback_to_class_doc* is true (by default), retrieve
632+
the documentation string from the class of the object.
633+
628634
Return ``None`` if the documentation string is invalid or missing.
629635

630636
.. versionchanged:: 3.5
631637
Documentation strings are now inherited if not overridden.
632638

639+
.. versionchanged:: next
640+
Added parameters *inherit_class_doc* and *fallback_to_class_doc*.
641+
633642

634643
.. function:: getcomments(object)
635644

Doc/whatsnew/3.15.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,14 @@ http.cookies
429429
(Contributed by Nick Burns and Senthil Kumaran in :gh:`92936`.)
430430

431431

432+
inspect
433+
-------
434+
435+
* Add parameters *inherit_class_doc* and *fallback_to_class_doc*
436+
for :func:`~inspect.getdoc`.
437+
(Contributed by Serhiy Storchaka in :gh:`132686`.)
438+
439+
432440
locale
433441
------
434442

Lib/inspect.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -706,8 +706,8 @@ def _findclass(func):
706706
return None
707707
return cls
708708

709-
def _finddoc(obj):
710-
if isclass(obj):
709+
def _finddoc(obj, *, search_in_class=True):
710+
if search_in_class and isclass(obj):
711711
for base in obj.__mro__:
712712
if base is not object:
713713
try:
@@ -767,19 +767,37 @@ def _finddoc(obj):
767767
return doc
768768
return None
769769

770-
def getdoc(object):
770+
def _getowndoc(obj):
771+
"""Get the documentation string for an object if it is not
772+
inherited from its class."""
773+
try:
774+
doc = object.__getattribute__(obj, '__doc__')
775+
if doc is None:
776+
return None
777+
if obj is not type:
778+
typedoc = type(obj).__doc__
779+
if isinstance(typedoc, str) and typedoc == doc:
780+
return None
781+
return doc
782+
except AttributeError:
783+
return None
784+
785+
def getdoc(object, *, fallback_to_class_doc=True, inherit_class_doc=True):
771786
"""Get the documentation string for an object.
772787
773788
All tabs are expanded to spaces. To clean up docstrings that are
774789
indented to line up with blocks of code, any whitespace than can be
775790
uniformly removed from the second line onwards is removed."""
776-
try:
777-
doc = object.__doc__
778-
except AttributeError:
779-
return None
791+
if fallback_to_class_doc:
792+
try:
793+
doc = object.__doc__
794+
except AttributeError:
795+
return None
796+
else:
797+
doc = _getowndoc(object)
780798
if doc is None:
781799
try:
782-
doc = _finddoc(object)
800+
doc = _finddoc(object, search_in_class=inherit_class_doc)
783801
except (AttributeError, TypeError):
784802
return None
785803
if not isinstance(doc, str):

Lib/pydoc.py

Lines changed: 3 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -108,96 +108,10 @@ def pathdirs():
108108
normdirs.append(normdir)
109109
return dirs
110110

111-
def _findclass(func):
112-
cls = sys.modules.get(func.__module__)
113-
if cls is None:
114-
return None
115-
for name in func.__qualname__.split('.')[:-1]:
116-
cls = getattr(cls, name)
117-
if not inspect.isclass(cls):
118-
return None
119-
return cls
120-
121-
def _finddoc(obj):
122-
if inspect.ismethod(obj):
123-
name = obj.__func__.__name__
124-
self = obj.__self__
125-
if (inspect.isclass(self) and
126-
getattr(getattr(self, name, None), '__func__') is obj.__func__):
127-
# classmethod
128-
cls = self
129-
else:
130-
cls = self.__class__
131-
elif inspect.isfunction(obj):
132-
name = obj.__name__
133-
cls = _findclass(obj)
134-
if cls is None or getattr(cls, name) is not obj:
135-
return None
136-
elif inspect.isbuiltin(obj):
137-
name = obj.__name__
138-
self = obj.__self__
139-
if (inspect.isclass(self) and
140-
self.__qualname__ + '.' + name == obj.__qualname__):
141-
# classmethod
142-
cls = self
143-
else:
144-
cls = self.__class__
145-
# Should be tested before isdatadescriptor().
146-
elif isinstance(obj, property):
147-
name = obj.__name__
148-
cls = _findclass(obj.fget)
149-
if cls is None or getattr(cls, name) is not obj:
150-
return None
151-
elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj):
152-
name = obj.__name__
153-
cls = obj.__objclass__
154-
if getattr(cls, name) is not obj:
155-
return None
156-
if inspect.ismemberdescriptor(obj):
157-
slots = getattr(cls, '__slots__', None)
158-
if isinstance(slots, dict) and name in slots:
159-
return slots[name]
160-
else:
161-
return None
162-
for base in cls.__mro__:
163-
try:
164-
doc = _getowndoc(getattr(base, name))
165-
except AttributeError:
166-
continue
167-
if doc is not None:
168-
return doc
169-
return None
170-
171-
def _getowndoc(obj):
172-
"""Get the documentation string for an object if it is not
173-
inherited from its class."""
174-
try:
175-
doc = object.__getattribute__(obj, '__doc__')
176-
if doc is None:
177-
return None
178-
if obj is not type:
179-
typedoc = type(obj).__doc__
180-
if isinstance(typedoc, str) and typedoc == doc:
181-
return None
182-
return doc
183-
except AttributeError:
184-
return None
185-
186111
def _getdoc(object):
187-
"""Get the documentation string for an object.
188-
189-
All tabs are expanded to spaces. To clean up docstrings that are
190-
indented to line up with blocks of code, any whitespace than can be
191-
uniformly removed from the second line onwards is removed."""
192-
doc = _getowndoc(object)
193-
if doc is None:
194-
try:
195-
doc = _finddoc(object)
196-
except (AttributeError, TypeError):
197-
return None
198-
if not isinstance(doc, str):
199-
return None
200-
return inspect.cleandoc(doc)
112+
return inspect.getdoc(object,
113+
fallback_to_class_doc=False,
114+
inherit_class_doc=False)
201115

202116
def getdoc(object):
203117
"""Get the doc string or comments for an object."""

Lib/test/test_inspect/test_inspect.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,10 +688,37 @@ def test_getdoc_inherited(self):
688688
self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction),
689689
'The automatic gainsaying.')
690690

691+
@unittest.skipIf(sys.flags.optimize >= 2,
692+
"Docstrings are omitted with -O2 and above")
693+
def test_getdoc_inherited_class_doc(self):
694+
class A:
695+
"""Common base class"""
696+
class B(A):
697+
pass
698+
699+
a = A()
700+
self.assertEqual(inspect.getdoc(A), 'Common base class')
701+
self.assertEqual(inspect.getdoc(A, inherit_class_doc=False),
702+
'Common base class')
703+
self.assertEqual(inspect.getdoc(a), 'Common base class')
704+
self.assertIsNone(inspect.getdoc(a, fallback_to_class_doc=False))
705+
a.__doc__ = 'Instance'
706+
self.assertEqual(inspect.getdoc(a, fallback_to_class_doc=False),
707+
'Instance')
708+
709+
b = B()
710+
self.assertEqual(inspect.getdoc(B), 'Common base class')
711+
self.assertIsNone(inspect.getdoc(B, inherit_class_doc=False))
712+
self.assertIsNone(inspect.getdoc(b))
713+
self.assertIsNone(inspect.getdoc(b, fallback_to_class_doc=False))
714+
b.__doc__ = 'Instance'
715+
self.assertEqual(inspect.getdoc(b, fallback_to_class_doc=False), 'Instance')
716+
691717
@unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings")
692718
def test_finddoc(self):
693719
finddoc = inspect._finddoc
694720
self.assertEqual(finddoc(int), int.__doc__)
721+
self.assertIsNone(finddoc(int, search_in_class=False))
695722
self.assertEqual(finddoc(int.to_bytes), int.to_bytes.__doc__)
696723
self.assertEqual(finddoc(int().to_bytes), int.to_bytes.__doc__)
697724
self.assertEqual(finddoc(int.from_bytes), int.from_bytes.__doc__)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add parameters *inherit_class_doc* and *fallback_to_class_doc* for
2+
:func:`inspect.getdoc`.

0 commit comments

Comments
 (0)