From b2ccea56afe80eb893d429cd153bdcdfb344d5a2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 11 Apr 2020 23:55:12 +0300 Subject: [PATCH 1/2] bpo-40257: Output object's own docstring in pydoc --- Lib/inspect.py | 33 +++++++++++++++++---------------- Lib/pydoc.py | 19 ++++++++----------- Lib/test/test_inspect.py | 15 ++++++++++++--- Lib/test/test_pydoc.py | 3 ++- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index 90435a10caac77..6f7d5cd19ce4c6 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -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) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index f172700a15f9d9..a89b8045709c27 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -825,11 +825,8 @@ def spilldata(msg, attrs, predicate): 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('
%s
\n' % base) else: doc = self.markup(getdoc(value), self.preformat, @@ -1309,10 +1306,7 @@ def spilldata(msg, attrs, predicate): 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 @@ def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=No 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) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index f193807e80473a..2dc8454595e175 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -439,8 +439,7 @@ def test_getdoc(self): @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 @@ 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.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__) diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index ebd8d4a6c3db10..800913b425a258 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -1254,7 +1254,8 @@ class X: X.attr.__doc__ = 'Custom descriptor' self.assertEqual(self._get_summary_lines(X.attr), """\ -.Descr object>""") +.Descr object> + Custom descriptor""") X.attr.__name__ = 'foo' self.assertEqual(self._get_summary_lines(X.attr), """\ From 99048a3a1ea50a5491f96ec2e1d533f37efb49a1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 15 Apr 2020 19:36:03 +0300 Subject: [PATCH 2/2] Add docs. --- Doc/library/inspect.rst | 5 ++++- Doc/whatsnew/3.9.rst | 13 +++++++++++++ .../2020-04-15-19-34-11.bpo-40257.ux8FUr.rst | 5 +++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2020-04-15-19-34-11.bpo-40257.ux8FUr.rst diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index d00a30ff004063..634645124c7864 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -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) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index afb099a4ec5583..aae8e5b0c97164 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -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 ------------------------ 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 index 00000000000000..52247b2d1a7c14 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-04-15-19-34-11.bpo-40257.ux8FUr.rst @@ -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.