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.