https://github.com/python/cpython/commit/3e12d54923814dcca2a228d8f2bd0c69081d4974
commit: 3e12d54923814dcca2a228d8f2bd0c69081d4974
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-07T19:17:35+03:00
summary:

gh-155236: Add the dedent parameter in inspect.cleandoc() and inspect.getdoc() 
(GH-155237)

pydoc no longer dedents documentation strings, so the indentation of the
parameter descriptions generated by Argument Clinic is preserved and does
not depend on the rest of the docstring.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-08-05-16-30-12.gh-issue-155236.Kd7pQr.rst
M Doc/library/inspect.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 eddc98824d48106..3b34445f019c36c 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -707,9 +707,10 @@ attributes (see :ref:`import-mod-attrs` for module 
attributes):
 Retrieving source code
 ----------------------
 
-.. function:: getdoc(object, *, inherit_class_doc=True, 
fallback_to_class_doc=True)
+.. function:: getdoc(object, *, inherit_class_doc=True, 
fallback_to_class_doc=True, dedent=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`
+   (with the same meaning of *dedent*).
    If the documentation string for an object is not provided:
 
    * if the object is a class and *inherit_class_doc* is true (by default),
@@ -730,6 +731,9 @@ Retrieving source code
       Documentation strings on :class:`~functools.cached_property`
       objects are now inherited if not overridden.
 
+   .. versionchanged:: next
+      Added the *dedent* parameter.
+
 
 .. function:: getcomments(object)
 
@@ -791,15 +795,23 @@ Retrieving source code
       former.
 
 
-.. function:: cleandoc(doc)
+.. function:: cleandoc(doc, *, dedent=True)
 
    Clean up indentation from docstrings that are indented to line up with 
blocks
    of code.
 
    All leading whitespace is removed from the first line.  Any leading 
whitespace
-   that can be uniformly removed from the second line onwards is removed.  
Empty
-   lines at the beginning and end are subsequently removed.  Also, all tabs are
-   expanded to spaces.
+   that can be uniformly removed from the second line onwards is removed, 
unless
+   *dedent* is false.  Empty lines at the beginning and end are subsequently
+   removed.  Also, all tabs are expanded to spaces.
+
+   Since Python 3.13 the compiler removes the indentation of docstrings, so
+   *dedent* only affects documentation strings which are not written as
+   docstrings in the source code, like those generated by Argument Clinic,
+   where the indentation is meaningful.
+
+   .. versionchanged:: next
+      Added the *dedent* parameter.
 
 
 .. _inspect-signature-object:
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 2a14e43b66f2fac..3f8991c79652d3f 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -793,12 +793,14 @@ def _getowndoc(obj):
     except AttributeError:
         return None
 
-def getdoc(object, *, fallback_to_class_doc=True, inherit_class_doc=True):
+def getdoc(object, *, fallback_to_class_doc=True, inherit_class_doc=True,
+           dedent=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."""
+    uniformly removed from the second line onwards is removed, unless
+    dedent is false."""
     if fallback_to_class_doc:
         try:
             doc = object.__doc__
@@ -813,22 +815,23 @@ def getdoc(object, *, fallback_to_class_doc=True, 
inherit_class_doc=True):
             return None
     if not isinstance(doc, str):
         return None
-    return cleandoc(doc)
+    return cleandoc(doc, dedent=dedent)
 
-def cleandoc(doc):
+def cleandoc(doc, *, dedent=True):
     """Clean up indentation from docstrings.
 
     Any whitespace that can be uniformly removed from the second line
-    onwards is removed."""
+    onwards is removed, unless dedent is false."""
     lines = doc.expandtabs().split('\n')
 
     # Find minimum indentation of any non-blank lines after first line.
     margin = sys.maxsize
-    for line in lines[1:]:
-        content = len(line.lstrip(' '))
-        if content:
-            indent = len(line) - content
-            margin = min(margin, indent)
+    if dedent:
+        for line in lines[1:]:
+            content = len(line.lstrip(' '))
+            if content:
+                indent = len(line) - content
+                margin = min(margin, indent)
     # Remove indentation.
     if lines:
         lines[0] = lines[0].lstrip(' ')
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 72974af26bee64c..3cba08b83af6811 100644
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -130,9 +130,12 @@ def pathdirs():
     return dirs
 
 def _getdoc(object):
+    # Docstrings written in the source are dedented by the compiler; the
+    # indentation of generated docstrings is meaningful.
     return inspect.getdoc(object,
                           fallback_to_class_doc=False,
-                          inherit_class_doc=False)
+                          inherit_class_doc=False,
+                          dedent=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 5153e5eb9a4ff8b..844811692df2d6d 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -781,6 +781,22 @@ def test_cleandoc(self):
             with self.subTest(i=i):
                 self.assertEqual(func(input), expected)
 
+    def test_cleandoc_no_dedent(self):
+        func = inspect.cleandoc
+        self.assertEqual(func('An\n  indented\n   docstring.', dedent=False),
+                         'An\n  indented\n   docstring.')
+        # Everything else that cleandoc() does still applies.
+        self.assertEqual(func('  An\n\n\tindented\n\n', dedent=False),
+                         'An\n\n        indented')
+
+    def test_getdoc_no_dedent(self):
+        class C:
+            pass
+        # Written as a docstring, it would be dedented by the compiler.
+        C.__doc__ = 'Summary.\n\n  param\n    description'
+        self.assertEqual(inspect.getdoc(C, dedent=False), C.__doc__)
+        self.assertEqual(inspect.getdoc(C), 'Summary.\n\nparam\n  description')
+
     @cpython_only
     def test_c_cleandoc(self):
         try:
diff --git 
a/Misc/NEWS.d/next/Library/2026-08-05-16-30-12.gh-issue-155236.Kd7pQr.rst 
b/Misc/NEWS.d/next/Library/2026-08-05-16-30-12.gh-issue-155236.Kd7pQr.rst
new file mode 100644
index 000000000000000..7e8a7a3c280a6d2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-05-16-30-12.gh-issue-155236.Kd7pQr.rst
@@ -0,0 +1,4 @@
+Add the *dedent* parameter in :func:`inspect.cleandoc` and
+:func:`inspect.getdoc`.
+:mod:`pydoc` no longer dedents documentation strings, so the indentation
+of the parameter descriptions generated by Argument Clinic is preserved.

_______________________________________________
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