Skip to content

Commit a72dd03

Browse files
bpo-40257: Revert changes to inspect.getdoc()
1 parent a15c9b3 commit a72dd03

File tree

6 files changed

+115
-40
lines changed

6 files changed

+115
-40
lines changed

Doc/library/inspect.rst

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -473,15 +473,12 @@ Retrieving source code
473473

474474
Get the documentation string for an object, cleaned up with :func:`cleandoc`.
475475
If the documentation string for an object is not provided and the object is
476-
a method, a property or a descriptor, retrieve the documentation
476+
a class, a method, a property or a descriptor, retrieve the documentation
477477
string from the inheritance hierarchy.
478478

479479
.. versionchanged:: 3.5
480480
Documentation strings are now inherited if not overridden.
481481

482-
.. versionchanged:: 3.9
483-
Documentation strings for classes are no longer inherited.
484-
485482

486483
.. function:: getcomments(object)
487484

Doc/whatsnew/3.9.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -771,11 +771,6 @@ Changes in the Python API
771771
:class:`ftplib.FTP_TLS` as a keyword-only parameter, and the default encoding
772772
is changed from Latin-1 to UTF-8 to follow :rfc:`2640`.
773773

774-
* :func:`inspect.getdoc` no longer returns docstring inherited from the type
775-
of the object or from parent class if it is a class if it is not defined
776-
in the object itself.
777-
(Contributed by Serhiy Storchaka in :issue:`40257`.)
778-
779774
* :meth:`asyncio.loop.shutdown_default_executor` has been added to
780775
:class:`~asyncio.AbstractEventLoop`, meaning alternative event loops that
781776
inherit from it should have this method defined.

Lib/inspect.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,17 @@ def _findclass(func):
543543
return cls
544544

545545
def _finddoc(obj):
546+
if isclass(obj):
547+
for base in obj.__mro__:
548+
if base is not object:
549+
try:
550+
doc = base.__doc__
551+
except AttributeError:
552+
continue
553+
if doc is not None:
554+
return doc
555+
return None
556+
546557
if ismethod(obj):
547558
name = obj.__func__.__name__
548559
self = obj.__self__
@@ -586,35 +597,23 @@ def _finddoc(obj):
586597
return None
587598
for base in cls.__mro__:
588599
try:
589-
doc = _getowndoc(getattr(base, name))
600+
doc = getattr(base, name).__doc__
590601
except AttributeError:
591602
continue
592603
if doc is not None:
593604
return doc
594605
return None
595606

596-
def _getowndoc(obj):
597-
"""Get the documentation string for an object if it is not
598-
inherited from its class."""
599-
try:
600-
doc = object.__getattribute__(obj, '__doc__')
601-
if doc is None:
602-
return None
603-
if obj is not type:
604-
typedoc = type(obj).__doc__
605-
if isinstance(typedoc, str) and typedoc == doc:
606-
return None
607-
return doc
608-
except AttributeError:
609-
return None
610-
611607
def getdoc(object):
612608
"""Get the documentation string for an object.
613609
614610
All tabs are expanded to spaces. To clean up docstrings that are
615611
indented to line up with blocks of code, any whitespace than can be
616612
uniformly removed from the second line onwards is removed."""
617-
doc = _getowndoc(object)
613+
try:
614+
doc = object.__doc__
615+
except AttributeError:
616+
return None
618617
if doc is None:
619618
try:
620619
doc = _finddoc(object)

Lib/pydoc.py

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,101 @@ def pathdirs():
9090
normdirs.append(normdir)
9191
return dirs
9292

93+
def _findclass(func):
94+
cls = sys.modules.get(func.__module__)
95+
if cls is None:
96+
return None
97+
for name in func.__qualname__.split('.')[:-1]:
98+
cls = getattr(cls, name)
99+
if not inspect.isclass(cls):
100+
return None
101+
return cls
102+
103+
def _finddoc(obj):
104+
if inspect.ismethod(obj):
105+
name = obj.__func__.__name__
106+
self = obj.__self__
107+
if (inspect.isclass(self) and
108+
getattr(getattr(self, name, None), '__func__') is obj.__func__):
109+
# classmethod
110+
cls = self
111+
else:
112+
cls = self.__class__
113+
elif inspect.isfunction(obj):
114+
name = obj.__name__
115+
cls = _findclass(obj)
116+
if cls is None or getattr(cls, name) is not obj:
117+
return None
118+
elif inspect.isbuiltin(obj):
119+
name = obj.__name__
120+
self = obj.__self__
121+
if (inspect.isclass(self) and
122+
self.__qualname__ + '.' + name == obj.__qualname__):
123+
# classmethod
124+
cls = self
125+
else:
126+
cls = self.__class__
127+
# Should be tested before isdatadescriptor().
128+
elif isinstance(obj, property):
129+
func = obj.fget
130+
name = func.__name__
131+
cls = _findclass(func)
132+
if cls is None or getattr(cls, name) is not obj:
133+
return None
134+
elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj):
135+
name = obj.__name__
136+
cls = obj.__objclass__
137+
if getattr(cls, name) is not obj:
138+
return None
139+
if inspect.ismemberdescriptor(obj):
140+
slots = getattr(cls, '__slots__', None)
141+
if isinstance(slots, dict) and name in slots:
142+
return slots[name]
143+
else:
144+
return None
145+
for base in cls.__mro__:
146+
try:
147+
doc = _getowndoc(getattr(base, name))
148+
except AttributeError:
149+
continue
150+
if doc is not None:
151+
return doc
152+
return None
153+
154+
def _getowndoc(obj):
155+
"""Get the documentation string for an object if it is not
156+
inherited from its class."""
157+
try:
158+
doc = object.__getattribute__(obj, '__doc__')
159+
if doc is None:
160+
return None
161+
if obj is not type:
162+
typedoc = type(obj).__doc__
163+
if isinstance(typedoc, str) and typedoc == doc:
164+
return None
165+
return doc
166+
except AttributeError:
167+
return None
168+
169+
def _getdoc(object):
170+
"""Get the documentation string for an object.
171+
172+
All tabs are expanded to spaces. To clean up docstrings that are
173+
indented to line up with blocks of code, any whitespace than can be
174+
uniformly removed from the second line onwards is removed."""
175+
doc = _getowndoc(object)
176+
if doc is None:
177+
try:
178+
doc = _finddoc(object)
179+
except (AttributeError, TypeError):
180+
return None
181+
if not isinstance(doc, str):
182+
return None
183+
return inspect.cleandoc(doc)
184+
93185
def getdoc(object):
94186
"""Get the doc string or comments for an object."""
95-
result = inspect.getdoc(object) or inspect.getcomments(object)
187+
result = _getdoc(object) or inspect.getcomments(object)
96188
return result and re.sub('^ *\n', '', result.rstrip()) or ''
97189

98190
def splitdoc(doc):
@@ -1669,7 +1761,7 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
16691761
inspect.isclass(object) or
16701762
inspect.isroutine(object) or
16711763
inspect.isdatadescriptor(object) or
1672-
inspect.getdoc(object)):
1764+
_getdoc(object)):
16731765
# If the passed object is a piece of data or an instance,
16741766
# document its available methods instead of its value.
16751767
if hasattr(object, '__origin__'):

Lib/test/test_inspect.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -439,28 +439,19 @@ def test_getdoc(self):
439439
@unittest.skipIf(sys.flags.optimize >= 2,
440440
"Docstrings are omitted with -O2 and above")
441441
def test_getdoc_inherited(self):
442-
self.assertIsNone(inspect.getdoc(mod.FesteringGob))
442+
self.assertEqual(inspect.getdoc(mod.FesteringGob),
443+
'A longer,\n\nindented\n\ndocstring.')
443444
self.assertEqual(inspect.getdoc(mod.FesteringGob.abuse),
444445
'Another\n\ndocstring\n\ncontaining\n\ntabs')
445446
self.assertEqual(inspect.getdoc(mod.FesteringGob().abuse),
446447
'Another\n\ndocstring\n\ncontaining\n\ntabs')
447448
self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction),
448449
'The automatic gainsaying.')
449450

450-
@unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings")
451-
def test_getowndoc(self):
452-
getowndoc = inspect._getowndoc
453-
self.assertEqual(getowndoc(type), type.__doc__)
454-
self.assertEqual(getowndoc(int), int.__doc__)
455-
self.assertEqual(getowndoc(int.to_bytes), int.to_bytes.__doc__)
456-
self.assertEqual(getowndoc(int().to_bytes), int.to_bytes.__doc__)
457-
self.assertEqual(getowndoc(int.from_bytes), int.from_bytes.__doc__)
458-
self.assertEqual(getowndoc(int.real), int.real.__doc__)
459-
460451
@unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings")
461452
def test_finddoc(self):
462453
finddoc = inspect._finddoc
463-
self.assertIsNone(finddoc(int))
454+
self.assertEqual(finddoc(int), int.__doc__)
464455
self.assertEqual(finddoc(int.to_bytes), int.to_bytes.__doc__)
465456
self.assertEqual(finddoc(int().to_bytes), int.to_bytes.__doc__)
466457
self.assertEqual(finddoc(int.from_bytes), int.from_bytes.__doc__)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Revert changes to :func:`inspect.getdoc`.

0 commit comments

Comments
 (0)