gh-132686: Add parameters inherit_class_doc and fallback_to_class_doc for inspect.getdoc() (GH-132691)

This commit is contained in:
Serhiy Storchaka 2025-11-12 00:01:25 +02:00 committed by GitHub
parent c744ccb2c9
commit 7906f4d96a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 79 additions and 101 deletions

View file

@ -619,17 +619,26 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
Retrieving source code Retrieving source code
---------------------- ----------------------
.. function:: getdoc(object) .. function:: getdoc(object, *, inherit_class_doc=True, fallback_to_class_doc=True)
Get the documentation string for an object, cleaned up with :func:`cleandoc`. 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 If the documentation string for an object is not provided:
a class, a method, a property or a descriptor, retrieve the documentation
string from the inheritance hierarchy. * if the object is a class and *inherit_class_doc* is true (by default),
retrieve the documentation string from the inheritance hierarchy;
* if the object is a method, a property or a descriptor, retrieve
the documentation string from the inheritance hierarchy;
* otherwise, if *fallback_to_class_doc* is true (by default), retrieve
the documentation string from the class of the object.
Return ``None`` if the documentation string is invalid or missing. Return ``None`` if the documentation string is invalid or missing.
.. versionchanged:: 3.5 .. versionchanged:: 3.5
Documentation strings are now inherited if not overridden. Documentation strings are now inherited if not overridden.
.. versionchanged:: next
Added parameters *inherit_class_doc* and *fallback_to_class_doc*.
.. function:: getcomments(object) .. function:: getcomments(object)

View file

@ -429,6 +429,14 @@ http.cookies
(Contributed by Nick Burns and Senthil Kumaran in :gh:`92936`.) (Contributed by Nick Burns and Senthil Kumaran in :gh:`92936`.)
inspect
-------
* Add parameters *inherit_class_doc* and *fallback_to_class_doc*
for :func:`~inspect.getdoc`.
(Contributed by Serhiy Storchaka in :gh:`132686`.)
locale locale
------ ------

View file

@ -706,8 +706,8 @@ def _findclass(func):
return None return None
return cls return cls
def _finddoc(obj): def _finddoc(obj, *, search_in_class=True):
if isclass(obj): if search_in_class and isclass(obj):
for base in obj.__mro__: for base in obj.__mro__:
if base is not object: if base is not object:
try: try:
@ -767,19 +767,37 @@ def _finddoc(obj):
return doc return doc
return None return None
def getdoc(object): def _getowndoc(obj):
"""Get the documentation string for an object if it is not
inherited from its class."""
try:
doc = object.__getattribute__(obj, '__doc__')
if doc is None:
return None
if obj is not type:
typedoc = type(obj).__doc__
if isinstance(typedoc, str) and typedoc == doc:
return None
return doc
except AttributeError:
return None
def getdoc(object, *, fallback_to_class_doc=True, inherit_class_doc=True):
"""Get the documentation string for an object. """Get the documentation string for an object.
All tabs are expanded to spaces. To clean up docstrings that are All tabs are expanded to spaces. To clean up docstrings that are
indented to line up with blocks of code, any whitespace than can be indented to line up with blocks of code, any whitespace than can be
uniformly removed from the second line onwards is removed.""" uniformly removed from the second line onwards is removed."""
try: if fallback_to_class_doc:
doc = object.__doc__ try:
except AttributeError: doc = object.__doc__
return None except AttributeError:
return None
else:
doc = _getowndoc(object)
if doc is None: if doc is None:
try: try:
doc = _finddoc(object) doc = _finddoc(object, search_in_class=inherit_class_doc)
except (AttributeError, TypeError): except (AttributeError, TypeError):
return None return None
if not isinstance(doc, str): if not isinstance(doc, str):

View file

@ -108,96 +108,10 @@ def pathdirs():
normdirs.append(normdir) normdirs.append(normdir)
return dirs return dirs
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 inspect.isclass(cls):
return None
return cls
def _finddoc(obj):
if inspect.ismethod(obj):
name = obj.__func__.__name__
self = obj.__self__
if (inspect.isclass(self) and
getattr(getattr(self, name, None), '__func__') is obj.__func__):
# classmethod
cls = self
else:
cls = self.__class__
elif inspect.isfunction(obj):
name = obj.__name__
cls = _findclass(obj)
if cls is None or getattr(cls, name) is not obj:
return None
elif inspect.isbuiltin(obj):
name = obj.__name__
self = obj.__self__
if (inspect.isclass(self) and
self.__qualname__ + '.' + name == obj.__qualname__):
# classmethod
cls = self
else:
cls = self.__class__
# Should be tested before isdatadescriptor().
elif isinstance(obj, property):
name = obj.__name__
cls = _findclass(obj.fget)
if cls is None or getattr(cls, name) is not obj:
return None
elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj):
name = obj.__name__
cls = obj.__objclass__
if getattr(cls, name) is not obj:
return None
if inspect.ismemberdescriptor(obj):
slots = getattr(cls, '__slots__', None)
if isinstance(slots, dict) and name in slots:
return slots[name]
else:
return None
for base in cls.__mro__:
try:
doc = _getowndoc(getattr(base, name))
except AttributeError:
continue
if doc is not None:
return doc
return None
def _getowndoc(obj):
"""Get the documentation string for an object if it is not
inherited from its class."""
try:
doc = object.__getattribute__(obj, '__doc__')
if doc is None:
return None
if obj is not type:
typedoc = type(obj).__doc__
if isinstance(typedoc, str) and typedoc == doc:
return None
return doc
except AttributeError:
return None
def _getdoc(object): def _getdoc(object):
"""Get the documentation string for an object. return inspect.getdoc(object,
fallback_to_class_doc=False,
All tabs are expanded to spaces. To clean up docstrings that are inherit_class_doc=False)
indented to line up with blocks of code, any whitespace than can be
uniformly removed from the second line onwards is removed."""
doc = _getowndoc(object)
if doc is None:
try:
doc = _finddoc(object)
except (AttributeError, TypeError):
return None
if not isinstance(doc, str):
return None
return inspect.cleandoc(doc)
def getdoc(object): def getdoc(object):
"""Get the doc string or comments for an object.""" """Get the doc string or comments for an object."""

View file

@ -688,10 +688,37 @@ def test_getdoc_inherited(self):
self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction), self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction),
'The automatic gainsaying.') 'The automatic gainsaying.')
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_getdoc_inherited_class_doc(self):
class A:
"""Common base class"""
class B(A):
pass
a = A()
self.assertEqual(inspect.getdoc(A), 'Common base class')
self.assertEqual(inspect.getdoc(A, inherit_class_doc=False),
'Common base class')
self.assertEqual(inspect.getdoc(a), 'Common base class')
self.assertIsNone(inspect.getdoc(a, fallback_to_class_doc=False))
a.__doc__ = 'Instance'
self.assertEqual(inspect.getdoc(a, fallback_to_class_doc=False),
'Instance')
b = B()
self.assertEqual(inspect.getdoc(B), 'Common base class')
self.assertIsNone(inspect.getdoc(B, inherit_class_doc=False))
self.assertIsNone(inspect.getdoc(b))
self.assertIsNone(inspect.getdoc(b, fallback_to_class_doc=False))
b.__doc__ = 'Instance'
self.assertEqual(inspect.getdoc(b, fallback_to_class_doc=False), 'Instance')
@unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings") @unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings")
def test_finddoc(self): def test_finddoc(self):
finddoc = inspect._finddoc finddoc = inspect._finddoc
self.assertEqual(finddoc(int), int.__doc__) self.assertEqual(finddoc(int), int.__doc__)
self.assertIsNone(finddoc(int, search_in_class=False))
self.assertEqual(finddoc(int.to_bytes), int.to_bytes.__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().to_bytes), int.to_bytes.__doc__)
self.assertEqual(finddoc(int.from_bytes), int.from_bytes.__doc__) self.assertEqual(finddoc(int.from_bytes), int.from_bytes.__doc__)

View file

@ -0,0 +1,2 @@
Add parameters *inherit_class_doc* and *fallback_to_class_doc* for
:func:`inspect.getdoc`.