[Python-checkins] bpo-40257: Revert changes to inspect.getdoc() (GH-20073)

Serhiy Storchaka webhook-mailer at python.org
Mon May 18 13:25:15 EDT 2020


https://github.com/python/cpython/commit/08b47c367a08f571a986366aa33828d3951fa88d
commit: 08b47c367a08f571a986366aa33828d3951fa88d
branch: master
author: Serhiy Storchaka <storchaka at gmail.com>
committer: GitHub <noreply at github.com>
date: 2020-05-18T20:25:07+03:00
summary:

bpo-40257: Revert changes to inspect.getdoc() (GH-20073)

files:
A Misc/NEWS.d/next/Library/2020-05-13-23-10-25.bpo-40257.aR4TGp.rst
M Doc/library/inspect.rst
M Doc/whatsnew/3.9.rst
M Lib/inspect.py
M Lib/pydoc.py
M Lib/test/test_inspect.py

diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index 634645124c786..d00a30ff00406 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -473,15 +473,12 @@ 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 method, a property or a descriptor, retrieve the documentation
+   a class, 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)
 
diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst
index 66e7c6461ecd7..593f523828703 100644
--- a/Doc/whatsnew/3.9.rst
+++ b/Doc/whatsnew/3.9.rst
@@ -846,11 +846,6 @@ 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`.)
-
 * :meth:`asyncio.loop.shutdown_default_executor` has been added to
   :class:`~asyncio.AbstractEventLoop`, meaning alternative event loops that
   inherit from it should have this method defined.
diff --git a/Lib/inspect.py b/Lib/inspect.py
index ad7e8cb1203e7..887a3424057b6 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -543,6 +543,17 @@ 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__
@@ -586,35 +597,23 @@ def _finddoc(obj):
         return None
     for base in cls.__mro__:
         try:
-            doc = _getowndoc(getattr(base, name))
+            doc = getattr(base, name).__doc__
         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)
+    try:
+        doc = object.__doc__
+    except AttributeError:
+        return None
     if doc is None:
         try:
             doc = _finddoc(object)
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 898cc44b295ee..628f9fc7d1d1e 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -90,9 +90,101 @@ 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):
+        func = obj.fget
+        name = func.__name__
+        cls = _findclass(func)
+        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)
+
 def getdoc(object):
     """Get the doc string or comments for an object."""
-    result = inspect.getdoc(object) or inspect.getcomments(object)
+    result = _getdoc(object) or inspect.getcomments(object)
     return result and re.sub('^ *\n', '', result.rstrip()) or ''
 
 def splitdoc(doc):
@@ -1669,7 +1761,7 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
               inspect.isclass(object) or
               inspect.isroutine(object) or
               inspect.isdatadescriptor(object) or
-              inspect.getdoc(object)):
+              _getdoc(object)):
         # If the passed object is a piece of data or an instance,
         # document its available methods instead of its value.
         if hasattr(object, '__origin__'):
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 98a9c0a662a09..e3e2be52076c6 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -439,7 +439,8 @@ def test_getdoc(self):
     @unittest.skipIf(sys.flags.optimize >= 2,
                      "Docstrings are omitted with -O2 and above")
     def test_getdoc_inherited(self):
-        self.assertIsNone(inspect.getdoc(mod.FesteringGob))
+        self.assertEqual(inspect.getdoc(mod.FesteringGob),
+                         'A longer,\n\nindented\n\ndocstring.')
         self.assertEqual(inspect.getdoc(mod.FesteringGob.abuse),
                          'Another\n\ndocstring\n\ncontaining\n\ntabs')
         self.assertEqual(inspect.getdoc(mod.FesteringGob().abuse),
@@ -447,20 +448,10 @@ def test_getdoc_inherited(self):
         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.assertIsNone(finddoc(int))
+        self.assertEqual(finddoc(int), int.__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__)
diff --git a/Misc/NEWS.d/next/Library/2020-05-13-23-10-25.bpo-40257.aR4TGp.rst b/Misc/NEWS.d/next/Library/2020-05-13-23-10-25.bpo-40257.aR4TGp.rst
new file mode 100644
index 0000000000000..9d4037bc9aa79
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-05-13-23-10-25.bpo-40257.aR4TGp.rst
@@ -0,0 +1 @@
+Revert changes to :func:`inspect.getdoc`.



More information about the Python-checkins mailing list