bpo-40257: Revert changes to inspect.getdoc() (GH-20073)
This commit is contained in:
parent
98e42d1f88
commit
08b47c367a
|
@ -473,15 +473,12 @@ 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 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.
|
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)
|
||||||
|
|
||||||
|
|
|
@ -846,11 +846,6 @@ 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`.)
|
|
||||||
|
|
||||||
* :meth:`asyncio.loop.shutdown_default_executor` has been added to
|
* :meth:`asyncio.loop.shutdown_default_executor` has been added to
|
||||||
:class:`~asyncio.AbstractEventLoop`, meaning alternative event loops that
|
:class:`~asyncio.AbstractEventLoop`, meaning alternative event loops that
|
||||||
inherit from it should have this method defined.
|
inherit from it should have this method defined.
|
||||||
|
|
|
@ -543,6 +543,17 @@ 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__
|
||||||
|
@ -586,35 +597,23 @@ def _finddoc(obj):
|
||||||
return None
|
return None
|
||||||
for base in cls.__mro__:
|
for base in cls.__mro__:
|
||||||
try:
|
try:
|
||||||
doc = _getowndoc(getattr(base, name))
|
doc = getattr(base, name).__doc__
|
||||||
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."""
|
||||||
doc = _getowndoc(object)
|
try:
|
||||||
|
doc = object.__doc__
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
if doc is None:
|
if doc is None:
|
||||||
try:
|
try:
|
||||||
doc = _finddoc(object)
|
doc = _finddoc(object)
|
||||||
|
|
96
Lib/pydoc.py
96
Lib/pydoc.py
|
@ -90,9 +90,101 @@ def pathdirs():
|
||||||
normdirs.append(normdir)
|
normdirs.append(normdir)
|
||||||
return dirs
|
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):
|
def getdoc(object):
|
||||||
"""Get the doc string or comments for an 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 ''
|
return result and re.sub('^ *\n', '', result.rstrip()) or ''
|
||||||
|
|
||||||
def splitdoc(doc):
|
def splitdoc(doc):
|
||||||
|
@ -1669,7 +1761,7 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
|
||||||
inspect.isclass(object) or
|
inspect.isclass(object) or
|
||||||
inspect.isroutine(object) or
|
inspect.isroutine(object) or
|
||||||
inspect.isdatadescriptor(object) or
|
inspect.isdatadescriptor(object) or
|
||||||
inspect.getdoc(object)):
|
_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.
|
||||||
if hasattr(object, '__origin__'):
|
if hasattr(object, '__origin__'):
|
||||||
|
|
|
@ -439,7 +439,8 @@ 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.assertIsNone(inspect.getdoc(mod.FesteringGob))
|
self.assertEqual(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),
|
||||||
|
@ -447,20 +448,10 @@ 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.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().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__)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Revert changes to :func:`inspect.getdoc`.
|
Loading…
Reference in New Issue