bpo-40257: Output object's own docstring in pydoc (GH-19479)

This commit is contained in:
Serhiy Storchaka 2020-04-15 23:00:20 +03:00 committed by GitHub
parent ba1bcffe5c
commit fbf2786c4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 61 additions and 32 deletions

View File

@ -473,12 +473,15 @@ Retrieving source code
Get the documentation string for an object, cleaned up with :func:`cleandoc`. 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 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. string from the inheritance hierarchy.
.. versionchanged:: 3.5 .. versionchanged:: 3.5
Documentation strings are now inherited if not overridden. Documentation strings are now inherited if not overridden.
.. versionchanged:: 3.9
Documentation strings for classes are no longer inherited.
.. function:: getcomments(object) .. function:: getcomments(object)

View File

@ -346,6 +346,13 @@ pprint
:mod:`pprint` can now pretty-print :class:`types.SimpleNamespace`. :mod:`pprint` can now pretty-print :class:`types.SimpleNamespace`.
(Contributed by Carl Bordum Hansen in :issue:`37376`.) (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 signal
------ ------
@ -798,6 +805,12 @@ Changes in the Python API
:class:`ftplib.FTP_TLS` as a keyword-only parameter, and the default encoding :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`. 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 CPython bytecode changes
------------------------ ------------------------

View File

@ -542,17 +542,6 @@ def _findclass(func):
return cls return cls
def _finddoc(obj): 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): if ismethod(obj):
name = obj.__func__.__name__ name = obj.__func__.__name__
self = obj.__self__ self = obj.__self__
@ -596,23 +585,35 @@ def _finddoc(obj):
return None return None
for base in cls.__mro__: for base in cls.__mro__:
try: try:
doc = getattr(base, name).__doc__ doc = _getowndoc(getattr(base, name))
except AttributeError: except AttributeError:
continue continue
if doc is not None: if doc is not None:
return doc return doc
return None 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): def getdoc(object):
"""Get the documentation string for an object. """Get the documentation string for an object.
All tabs are expanded to spaces. To clean up docstrings that are 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 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."""
try: doc = _getowndoc(object)
doc = object.__doc__
except AttributeError:
return None
if doc is None: if doc is None:
try: try:
doc = _finddoc(object) doc = _finddoc(object)

View File

@ -825,11 +825,8 @@ class HTMLDoc(Doc):
push(msg) push(msg)
for name, kind, homecls, value in ok: for name, kind, homecls, value in ok:
base = self.docother(getattr(object, name), name, mod) base = self.docother(getattr(object, name), name, mod)
if callable(value) or inspect.isdatadescriptor(value): doc = getdoc(value)
doc = getattr(value, "__doc__", None) if not doc:
else:
doc = None
if doc is None:
push('<dl><dt>%s</dl>\n' % base) push('<dl><dt>%s</dl>\n' % base)
else: else:
doc = self.markup(getdoc(value), self.preformat, doc = self.markup(getdoc(value), self.preformat,
@ -1309,10 +1306,7 @@ location listed above.
hr.maybe() hr.maybe()
push(msg) push(msg)
for name, kind, homecls, value in ok: for name, kind, homecls, value in ok:
if callable(value) or inspect.isdatadescriptor(value): doc = getdoc(value)
doc = getdoc(value)
else:
doc = None
try: try:
obj = getattr(object, name) obj = getattr(object, name)
except AttributeError: except AttributeError:
@ -1448,7 +1442,9 @@ location listed above.
chop = maxlen - len(line) chop = maxlen - len(line)
if chop < 0: repr = repr[:chop] + '...' if chop < 0: repr = repr[:chop] + '...'
line = (name and self.bold(name) + ' = ' or '') + repr 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)) line += '\n' + self.indent(str(doc))
return line return line
@ -1672,7 +1668,8 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
if not (inspect.ismodule(object) or if not (inspect.ismodule(object) or
inspect.isclass(object) or inspect.isclass(object) or
inspect.isroutine(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, # If the passed object is a piece of data or an instance,
# document its available methods instead of its value. # document its available methods instead of its value.
object = type(object) object = type(object)

View File

@ -439,8 +439,7 @@ class TestRetrievingSourceCode(GetSourceBase):
@unittest.skipIf(sys.flags.optimize >= 2, @unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above") "Docstrings are omitted with -O2 and above")
def test_getdoc_inherited(self): def test_getdoc_inherited(self):
self.assertEqual(inspect.getdoc(mod.FesteringGob), self.assertIsNone(inspect.getdoc(mod.FesteringGob))
'A longer,\n\nindented\n\ndocstring.')
self.assertEqual(inspect.getdoc(mod.FesteringGob.abuse), self.assertEqual(inspect.getdoc(mod.FesteringGob.abuse),
'Another\n\ndocstring\n\ncontaining\n\ntabs') 'Another\n\ndocstring\n\ncontaining\n\ntabs')
self.assertEqual(inspect.getdoc(mod.FesteringGob().abuse), self.assertEqual(inspect.getdoc(mod.FesteringGob().abuse),
@ -448,10 +447,20 @@ class TestRetrievingSourceCode(GetSourceBase):
self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction), self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction),
'The automatic gainsaying.') '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") @unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings")
def test_finddoc(self): def test_finddoc(self):
finddoc = inspect._finddoc 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().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__) self.assertEqual(finddoc(int.from_bytes), int.from_bytes.__doc__)

View File

@ -1254,7 +1254,8 @@ cm(x) method of builtins.type instance
X.attr.__doc__ = 'Custom descriptor' X.attr.__doc__ = 'Custom descriptor'
self.assertEqual(self._get_summary_lines(X.attr), """\ 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' X.attr.__name__ = 'foo'
self.assertEqual(self._get_summary_lines(X.attr), """\ self.assertEqual(self._get_summary_lines(X.attr), """\

View File

@ -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.