]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-40257: Output object's own docstring in pydoc (GH-19479)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 15 Apr 2020 20:00:20 +0000 (23:00 +0300)
committerGitHub <noreply@github.com>
Wed, 15 Apr 2020 20:00:20 +0000 (23:00 +0300)
Doc/library/inspect.rst
Doc/whatsnew/3.9.rst
Lib/inspect.py
Lib/pydoc.py
Lib/test/test_inspect.py
Lib/test/test_pydoc.py
Misc/NEWS.d/next/Library/2020-04-15-19-34-11.bpo-40257.ux8FUr.rst [new file with mode: 0644]

index d00a30ff0040631656a161a349ec56b066808ab6..634645124c78644eb419a9b42ebc676a0b067b3e 100644 (file)
@@ -473,12 +473,15 @@ Retrieving source code
 
    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
+   a method, a property or a descriptor, retrieve the documentation
    string from the inheritance hierarchy.
 
    .. versionchanged:: 3.5
       Documentation strings are now inherited if not overridden.
 
+   .. versionchanged:: 3.9
+      Documentation strings for classes are no longer inherited.
+
 
 .. function:: getcomments(object)
 
index afb099a4ec558380bd127d24ddfc2da9e09cce12..aae8e5b0c9716436504702fc255b6470687e47cd 100644 (file)
@@ -346,6 +346,13 @@ pprint
 :mod:`pprint` can now pretty-print :class:`types.SimpleNamespace`.
 (Contributed by Carl Bordum Hansen in :issue:`37376`.)
 
+pydoc
+-----
+
+The documentation string is now shown not only for class, function,
+method etc, but for any object that has its own ``__doc__`` attribute.
+(Contributed by Serhiy Storchaka in :issue:`40257`.)
+
 signal
 ------
 
@@ -798,6 +805,12 @@ Changes in the Python API
   :class:`ftplib.FTP_TLS` as a keyword-only parameter, and the default encoding
   is changed from Latin-1 to UTF-8 to follow :rfc:`2640`.
 
+* :func:`inspect.getdoc` no longer returns docstring inherited from the type
+  of the object or from parent class if it is a class if it is not defined
+  in the object itself.
+  (Contributed by Serhiy Storchaka in :issue:`40257`.)
+
+
 CPython bytecode changes
 ------------------------
 
index 90435a10caac77a2a9118814086c60918613f8a1..6f7d5cd19ce4c624e2db1c2d84dfc2b82949967f 100644 (file)
@@ -542,17 +542,6 @@ def _findclass(func):
     return cls
 
 def _finddoc(obj):
-    if isclass(obj):
-        for base in obj.__mro__:
-            if base is not object:
-                try:
-                    doc = base.__doc__
-                except AttributeError:
-                    continue
-                if doc is not None:
-                    return doc
-        return None
-
     if ismethod(obj):
         name = obj.__func__.__name__
         self = obj.__self__
@@ -596,23 +585,35 @@ def _finddoc(obj):
         return None
     for base in cls.__mro__:
         try:
-            doc = getattr(base, name).__doc__
+            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."""
-    try:
-        doc = object.__doc__
-    except AttributeError:
-        return None
+    doc = _getowndoc(object)
     if doc is None:
         try:
             doc = _finddoc(object)
index f172700a15f9d94ecdd6ddf31effae2e71b36fb8..a89b8045709c27c2a7f2bedfd71ca6c46e190497 100755 (executable)
@@ -825,11 +825,8 @@ class HTMLDoc(Doc):
                 push(msg)
                 for name, kind, homecls, value in ok:
                     base = self.docother(getattr(object, name), name, mod)
-                    if callable(value) or inspect.isdatadescriptor(value):
-                        doc = getattr(value, "__doc__", None)
-                    else:
-                        doc = None
-                    if doc is None:
+                    doc = getdoc(value)
+                    if not doc:
                         push('<dl><dt>%s</dl>\n' % base)
                     else:
                         doc = self.markup(getdoc(value), self.preformat,
@@ -1309,10 +1306,7 @@ location listed above.
                 hr.maybe()
                 push(msg)
                 for name, kind, homecls, value in ok:
-                    if callable(value) or inspect.isdatadescriptor(value):
-                        doc = getdoc(value)
-                    else:
-                        doc = None
+                    doc = getdoc(value)
                     try:
                         obj = getattr(object, name)
                     except AttributeError:
@@ -1448,7 +1442,9 @@ location listed above.
             chop = maxlen - len(line)
             if chop < 0: repr = repr[:chop] + '...'
         line = (name and self.bold(name) + ' = ' or '') + repr
-        if doc is not None:
+        if not doc:
+            doc = getdoc(object)
+        if doc:
             line += '\n' + self.indent(str(doc))
         return line
 
@@ -1672,7 +1668,8 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
     if not (inspect.ismodule(object) or
               inspect.isclass(object) or
               inspect.isroutine(object) or
-              inspect.isdatadescriptor(object)):
+              inspect.isdatadescriptor(object) or
+              inspect.getdoc(object)):
         # If the passed object is a piece of data or an instance,
         # document its available methods instead of its value.
         object = type(object)
index f193807e80473ad3c8c0089980df9d2ffc092717..2dc8454595e175ee9cd607a71ff5ad6ccc947bec 100644 (file)
@@ -439,8 +439,7 @@ class TestRetrievingSourceCode(GetSourceBase):
     @unittest.skipIf(sys.flags.optimize >= 2,
                      "Docstrings are omitted with -O2 and above")
     def test_getdoc_inherited(self):
-        self.assertEqual(inspect.getdoc(mod.FesteringGob),
-                         'A longer,\n\nindented\n\ndocstring.')
+        self.assertIsNone(inspect.getdoc(mod.FesteringGob))
         self.assertEqual(inspect.getdoc(mod.FesteringGob.abuse),
                          'Another\n\ndocstring\n\ncontaining\n\ntabs')
         self.assertEqual(inspect.getdoc(mod.FesteringGob().abuse),
@@ -448,10 +447,20 @@ class TestRetrievingSourceCode(GetSourceBase):
         self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction),
                          'The automatic gainsaying.')
 
+    @unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings")
+    def test_getowndoc(self):
+        getowndoc = inspect._getowndoc
+        self.assertEqual(getowndoc(type), type.__doc__)
+        self.assertEqual(getowndoc(int), int.__doc__)
+        self.assertEqual(getowndoc(int.to_bytes), int.to_bytes.__doc__)
+        self.assertEqual(getowndoc(int().to_bytes), int.to_bytes.__doc__)
+        self.assertEqual(getowndoc(int.from_bytes), int.from_bytes.__doc__)
+        self.assertEqual(getowndoc(int.real), int.real.__doc__)
+
     @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))
         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__)
index ebd8d4a6c3db10775251272143f3d74dd1d046e0..800913b425a258b84e3e77fb91c7f7f4cbbf6caa 100644 (file)
@@ -1254,7 +1254,8 @@ cm(x) method of builtins.type instance
 
         X.attr.__doc__ = 'Custom descriptor'
         self.assertEqual(self._get_summary_lines(X.attr), """\
-<test.test_pydoc.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>""")
+<test.test_pydoc.TestDescriptions.test_custom_non_data_descriptor.<locals>.Descr object>
+    Custom descriptor""")
 
         X.attr.__name__ = 'foo'
         self.assertEqual(self._get_summary_lines(X.attr), """\
diff --git a/Misc/NEWS.d/next/Library/2020-04-15-19-34-11.bpo-40257.ux8FUr.rst b/Misc/NEWS.d/next/Library/2020-04-15-19-34-11.bpo-40257.ux8FUr.rst
new file mode 100644 (file)
index 0000000..52247b2
--- /dev/null
@@ -0,0 +1,5 @@
+func:`inspect.getdoc` no longer returns docstring inherited from the type of
+the object or from parent class if it is a class if it is not defined in the
+object itself. In :mod:`pydoc` the documentation string is now shown not
+only for class, function, method etc, but for any object that has its own
+``__doc__`` attribute.