https://github.com/python/cpython/commit/7906f4d96a8fffbee9f4d4991019595878ad54e9
commit: 7906f4d96a8fffbee9f4d4991019595878ad54e9
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-11-12T00:01:25+02:00
summary:

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

files:
A Misc/NEWS.d/next/Library/2025-04-18-18-08-05.gh-issue-132686.6kV_Gs.rst
M Doc/library/inspect.rst
M Doc/whatsnew/3.15.rst
M Lib/inspect.py
M Lib/pydoc.py
M Lib/test/test_inspect/test_inspect.py

diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index 2b3b294ff33a64..aff53b78c4a774 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -619,17 +619,26 @@ attributes (see :ref:`import-mod-attrs` for module 
attributes):
 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`.
-   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.
+   If the documentation string for an object is not provided:
+
+   * 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.
 
    .. versionchanged:: 3.5
       Documentation strings are now inherited if not overridden.
 
+   .. versionchanged:: next
+      Added parameters *inherit_class_doc* and *fallback_to_class_doc*.
+
 
 .. function:: getcomments(object)
 
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index ef18d36e4d4748..ecab0d03e105e6 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -429,6 +429,14 @@ http.cookies
   (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
 ------
 
diff --git a/Lib/inspect.py b/Lib/inspect.py
index bb22bab3040fcb..bb17848b444b67 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -706,8 +706,8 @@ def _findclass(func):
         return None
     return cls
 
-def _finddoc(obj):
-    if isclass(obj):
+def _finddoc(obj, *, search_in_class=True):
+    if search_in_class and isclass(obj):
         for base in obj.__mro__:
             if base is not object:
                 try:
@@ -767,19 +767,37 @@ def _finddoc(obj):
             return doc
     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.
 
     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
     uniformly removed from the second line onwards is removed."""
-    try:
-        doc = object.__doc__
-    except AttributeError:
-        return None
+    if fallback_to_class_doc:
+        try:
+            doc = object.__doc__
+        except AttributeError:
+            return None
+    else:
+        doc = _getowndoc(object)
     if doc is None:
         try:
-            doc = _finddoc(object)
+            doc = _finddoc(object, search_in_class=inherit_class_doc)
         except (AttributeError, TypeError):
             return None
     if not isinstance(doc, str):
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 989fbd517d8d83..45ff5fca308c14 100644
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -108,96 +108,10 @@ def pathdirs():
             normdirs.append(normdir)
     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):
-    """Get the documentation string for an object.
-
-    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
-    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)
+    return inspect.getdoc(object,
+                          fallback_to_class_doc=False,
+                          inherit_class_doc=False)
 
 def getdoc(object):
     """Get the doc string or comments for an object."""
diff --git a/Lib/test/test_inspect/test_inspect.py 
b/Lib/test/test_inspect/test_inspect.py
index d42f2dbff99cae..24fd4a2fa626d4 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -688,10 +688,37 @@ def test_getdoc_inherited(self):
         self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction),
                          '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")
     def test_finddoc(self):
         finddoc = inspect._finddoc
         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.from_bytes), int.from_bytes.__doc__)
diff --git 
a/Misc/NEWS.d/next/Library/2025-04-18-18-08-05.gh-issue-132686.6kV_Gs.rst 
b/Misc/NEWS.d/next/Library/2025-04-18-18-08-05.gh-issue-132686.6kV_Gs.rst
new file mode 100644
index 00000000000000..d0c8e2d705cc73
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-18-18-08-05.gh-issue-132686.6kV_Gs.rst
@@ -0,0 +1,2 @@
+Add parameters *inherit_class_doc* and *fallback_to_class_doc* for
+:func:`inspect.getdoc`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to