diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 14b99039b3bbda..4ff2f722bdc4c9 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -204,6 +204,19 @@ def classname(object, modname):
name = object.__module__ + '.' + name
return name
+def parentname(object, modname):
+ """Get a name of the enclosing class (qualified it with a module name
+ if necessary) or module."""
+ if '.' in object.__qualname__:
+ name = object.__qualname__.rpartition('.')[0]
+ if object.__module__ != modname:
+ return object.__module__ + '.' + name
+ else:
+ return name
+ else:
+ if object.__module__ != modname:
+ return object.__module__
+
def isdata(object):
"""Check if an object is of a type that probably means it's data."""
return not (inspect.ismodule(object) or inspect.isclass(object) or
@@ -298,13 +311,15 @@ def visiblename(name, all=None, obj=None):
return not name.startswith('_')
def classify_class_attrs(object):
- """Wrap inspect.classify_class_attrs, with fixup for data descriptors."""
+ """Wrap inspect.classify_class_attrs, with fixup for data descriptors and bound methods."""
results = []
for (name, kind, cls, value) in inspect.classify_class_attrs(object):
if inspect.isdatadescriptor(value):
kind = 'data descriptor'
if isinstance(value, property) and value.fset is None:
kind = 'readonly property'
+ elif kind == 'method' and _is_bound_method(value):
+ kind = 'static method'
results.append((name, kind, cls, value))
return results
@@ -653,6 +668,25 @@ def classlink(self, object, modname):
module.__name__, name, classname(object, modname))
return classname(object, modname)
+ def parentlink(self, object, modname):
+ """Make a link for the enclosing class or module."""
+ link = None
+ name, module = object.__name__, sys.modules.get(object.__module__)
+ if hasattr(module, name) and getattr(module, name) is object:
+ if '.' in object.__qualname__:
+ name = object.__qualname__.rpartition('.')[0]
+ if object.__module__ != modname:
+ link = '%s.html#%s' % (module.__name__, name)
+ else:
+ link = '#%s' % name
+ else:
+ if object.__module__ != modname:
+ link = '%s.html' % module.__name__
+ if link:
+ return '%s' % (link, parentname(object, modname))
+ else:
+ return parentname(object, modname)
+
def modulelink(self, object):
"""Make a link for a module."""
return '%s' % (object.__name__, object.__name__)
@@ -899,7 +933,7 @@ def spill(msg, attrs, predicate):
push(self.docdata(value, name, mod))
else:
push(self.document(value, name, mod,
- funcs, classes, mdict, object))
+ funcs, classes, mdict, object, homecls))
push('\n')
return attrs
@@ -1022,24 +1056,44 @@ def formatvalue(self, object):
return self.grey('=' + self.repr(object))
def docroutine(self, object, name=None, mod=None,
- funcs={}, classes={}, methods={}, cl=None):
+ funcs={}, classes={}, methods={}, cl=None, homecls=None):
"""Produce HTML documentation for a function or method object."""
realname = object.__name__
name = name or realname
- anchor = (cl and cl.__name__ or '') + '-' + name
+ if homecls is None:
+ homecls = cl
+ anchor = ('' if cl is None else cl.__name__) + '-' + name
note = ''
- skipdocs = 0
+ skipdocs = False
+ imfunc = None
if _is_bound_method(object):
- imclass = object.__self__.__class__
- if cl:
- if imclass is not cl:
- note = ' from ' + self.classlink(imclass, mod)
+ imself = object.__self__
+ if imself is cl:
+ imfunc = getattr(object, '__func__', None)
+ elif inspect.isclass(imself):
+ note = ' class method of %s' % self.classlink(imself, mod)
else:
- if object.__self__ is not None:
- note = ' method of %s instance' % self.classlink(
- object.__self__.__class__, mod)
- else:
- note = ' unbound %s method' % self.classlink(imclass,mod)
+ note = ' method of %s instance' % self.classlink(
+ imself.__class__, mod)
+ elif (inspect.ismethoddescriptor(object) or
+ inspect.ismethodwrapper(object)):
+ try:
+ objclass = object.__objclass__
+ except AttributeError:
+ pass
+ else:
+ if cl is None:
+ note = ' unbound %s method' % self.classlink(objclass, mod)
+ elif objclass is not homecls:
+ note = ' from ' + self.classlink(objclass, mod)
+ else:
+ imfunc = object
+ if inspect.isfunction(imfunc) and homecls is not None and (
+ imfunc.__module__ != homecls.__module__ or
+ imfunc.__qualname__ != homecls.__qualname__ + '.' + realname):
+ pname = self.parentlink(imfunc, mod)
+ if pname:
+ note = ' from %s' % pname
if (inspect.iscoroutinefunction(object) or
inspect.isasyncgenfunction(object)):
@@ -1050,10 +1104,13 @@ def docroutine(self, object, name=None, mod=None,
if name == realname:
title = '%s' % (anchor, realname)
else:
- if cl and inspect.getattr_static(cl, realname, []) is object:
+ if (cl is not None and
+ inspect.getattr_static(cl, realname, []) is object):
reallink = '%s' % (
cl.__name__ + '-' + realname, realname)
- skipdocs = 1
+ skipdocs = True
+ if note.startswith(' from '):
+ note = ''
else:
reallink = realname
title = '%s = %s' % (
@@ -1086,7 +1143,7 @@ def docroutine(self, object, name=None, mod=None,
doc = doc and '
%s' % doc
return '- %s
%s
\n' % (decl, doc)
- def docdata(self, object, name=None, mod=None, cl=None):
+ def docdata(self, object, name=None, mod=None, cl=None, *ignored):
"""Produce html documentation for a data descriptor."""
results = []
push = results.append
@@ -1198,7 +1255,7 @@ def formattree(self, tree, modname, parent=None, prefix=''):
entry, modname, c, prefix + ' ')
return result
- def docmodule(self, object, name=None, mod=None):
+ def docmodule(self, object, name=None, mod=None, *ignored):
"""Produce text documentation for a given module object."""
name = object.__name__ # ignore the passed-in name
synop, desc = splitdoc(getdoc(object))
@@ -1382,7 +1439,7 @@ def spill(msg, attrs, predicate):
push(self.docdata(value, name, mod))
else:
push(self.document(value,
- name, mod, object))
+ name, mod, object, homecls))
return attrs
def spilldescriptors(msg, attrs, predicate):
@@ -1457,23 +1514,43 @@ def formatvalue(self, object):
"""Format an argument default value as text."""
return '=' + self.repr(object)
- def docroutine(self, object, name=None, mod=None, cl=None):
+ def docroutine(self, object, name=None, mod=None, cl=None, homecls=None):
"""Produce text documentation for a function or method object."""
realname = object.__name__
name = name or realname
+ if homecls is None:
+ homecls = cl
note = ''
- skipdocs = 0
+ skipdocs = False
+ imfunc = None
if _is_bound_method(object):
- imclass = object.__self__.__class__
- if cl:
- if imclass is not cl:
- note = ' from ' + classname(imclass, mod)
+ imself = object.__self__
+ if imself is cl:
+ imfunc = getattr(object, '__func__', None)
+ elif inspect.isclass(imself):
+ note = ' class method of %s' % classname(imself, mod)
else:
- if object.__self__ is not None:
- note = ' method of %s instance' % classname(
- object.__self__.__class__, mod)
- else:
- note = ' unbound %s method' % classname(imclass,mod)
+ note = ' method of %s instance' % classname(
+ imself.__class__, mod)
+ elif (inspect.ismethoddescriptor(object) or
+ inspect.ismethodwrapper(object)):
+ try:
+ objclass = object.__objclass__
+ except AttributeError:
+ pass
+ else:
+ if cl is None:
+ note = ' unbound %s method' % classname(objclass, mod)
+ elif objclass is not homecls:
+ note = ' from ' + classname(objclass, mod)
+ else:
+ imfunc = object
+ if inspect.isfunction(imfunc) and homecls is not None and (
+ imfunc.__module__ != homecls.__module__ or
+ imfunc.__qualname__ != homecls.__qualname__ + '.' + realname):
+ pname = parentname(imfunc, mod)
+ if pname:
+ note = ' from %s' % pname
if (inspect.iscoroutinefunction(object) or
inspect.isasyncgenfunction(object)):
@@ -1484,8 +1561,11 @@ def docroutine(self, object, name=None, mod=None, cl=None):
if name == realname:
title = self.bold(realname)
else:
- if cl and inspect.getattr_static(cl, realname, []) is object:
- skipdocs = 1
+ if (cl is not None and
+ inspect.getattr_static(cl, realname, []) is object):
+ skipdocs = True
+ if note.startswith(' from '):
+ note = ''
title = self.bold(name) + ' = ' + realname
argspec = None
@@ -1512,7 +1592,7 @@ def docroutine(self, object, name=None, mod=None, cl=None):
doc = getdoc(object) or ''
return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n')
- def docdata(self, object, name=None, mod=None, cl=None):
+ def docdata(self, object, name=None, mod=None, cl=None, *ignored):
"""Produce text documentation for a data descriptor."""
results = []
push = results.append
@@ -1528,7 +1608,8 @@ def docdata(self, object, name=None, mod=None, cl=None):
docproperty = docdata
- def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None):
+ def docother(self, object, name=None, mod=None, parent=None, *ignored,
+ maxlen=None, doc=None):
"""Produce text documentation for a data object."""
repr = self.repr(object)
if maxlen:
diff --git a/Lib/test/pydocfodder.py b/Lib/test/pydocfodder.py
index a3ef2231243954..27037e048db819 100644
--- a/Lib/test/pydocfodder.py
+++ b/Lib/test/pydocfodder.py
@@ -2,6 +2,12 @@
import types
+def global_func(x, y):
+ """Module global function"""
+
+def global_func2(x, y):
+ """Module global function 2"""
+
class A:
"A class."
@@ -26,7 +32,7 @@ def A_classmethod(cls, x):
"A class method defined in A."
A_classmethod = classmethod(A_classmethod)
- def A_staticmethod():
+ def A_staticmethod(x, y):
"A static method defined in A."
A_staticmethod = staticmethod(A_staticmethod)
@@ -61,6 +67,28 @@ def BD_method(self):
def BCD_method(self):
"Method defined in B, C and D."
+ @classmethod
+ def B_classmethod(cls, x):
+ "A class method defined in B."
+
+ global_func = global_func # same name
+ global_func_alias = global_func
+ global_func2_alias = global_func2
+ B_classmethod_alias = B_classmethod
+ A_classmethod_ref = A.A_classmethod
+ A_staticmethod = A.A_staticmethod # same name
+ A_staticmethod_alias = A.A_staticmethod
+ A_method_ref = A().A_method
+ A_method_alias = A.A_method
+ B_method_alias = B_method
+ __repr__ = object.__repr__ # same name
+ object_repr = object.__repr__
+ get = {}.get # same name
+ dict_get = {}.get
+
+B.B_classmethod_ref = B.B_classmethod
+
+
class C(A):
"A class, derived from A."
@@ -136,3 +164,21 @@ def __call__(self, inst):
submodule = types.ModuleType(__name__ + '.submodule',
"""A submodule, which should appear in its parent's summary""")
+
+global_func_alias = global_func
+A_classmethod = A.A_classmethod # same name
+A_classmethod2 = A.A_classmethod
+A_classmethod3 = B.A_classmethod
+A_staticmethod = A.A_staticmethod # same name
+A_staticmethod_alias = A.A_staticmethod
+A_staticmethod_ref = A().A_staticmethod
+A_staticmethod_ref2 = B().A_staticmethod
+A_method = A().A_method # same name
+A_method2 = A().A_method
+A_method3 = B().A_method
+B_method = B.B_method # same name
+B_method2 = B.B_method
+count = list.count # same name
+list_count = list.count
+get = {}.get # same name
+dict_get = {}.get
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index ab2b45853c2214..8d668ac4399251 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -4443,22 +4443,22 @@ class Color(enum.Enum)
| The value of the Enum member.
|\x20\x20
| ----------------------------------------------------------------------
- | Methods inherited from enum.EnumType:
+ | Static methods inherited from enum.EnumType:
|\x20\x20
- | __contains__(member) from enum.EnumType
+ | __contains__(member)
| Return True if member is a member of this enum
| raises TypeError if member is not an enum member
|\x20\x20\x20\x20\x20\x20
| note: in 3.12 TypeError will no longer be raised, and True will also be
| returned if member is the value of a member in this enum
|\x20\x20
- | __getitem__(name) from enum.EnumType
+ | __getitem__(name)
| Return the member matching `name`.
|\x20\x20
- | __iter__() from enum.EnumType
+ | __iter__()
| Return members in definition order.
|\x20\x20
- | __len__() from enum.EnumType
+ | __len__()
| Return the number of members (no aliases)
|\x20\x20
| ----------------------------------------------------------------------
diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py
index 783ce07e04825f..7e97682db38366 100644
--- a/Lib/test/test_pydoc.py
+++ b/Lib/test/test_pydoc.py
@@ -22,6 +22,7 @@
from io import StringIO
from collections import namedtuple
from urllib.request import urlopen, urlcleanup
+from test import support
from test.support import import_helper
from test.support import os_helper
from test.support.script_helper import (assert_python_ok,
@@ -32,6 +33,7 @@
requires_docstrings, MISSING_C_DOCSTRINGS)
from test.support.os_helper import (TESTFN, rmtree, unlink)
from test import pydoc_mod
+from test import pydocfodder
class nonascii:
@@ -99,7 +101,7 @@ class C(builtins.object)
| ----------------------------------------------------------------------
| Class methods defined here:
|\x20\x20
- | __class_getitem__(item) from builtins.type
+ | __class_getitem__(item)
|\x20\x20
| ----------------------------------------------------------------------
| Data descriptors defined here:
@@ -163,7 +165,7 @@ class A(builtins.object)
Methods defined here:
__init__()
Wow, I have no function!
-
+ ----------------------------------------------------------------------
Data descriptors defined here:
__dict__
dictionary for instance variables
@@ -176,6 +178,7 @@ class B(builtins.object)
dictionary for instance variables
__weakref__
list of weak references to the object
+ ----------------------------------------------------------------------
Data and other attributes defined here:
NO_MEANING = 'eggs'
__annotations__ = {'NO_MEANING': }
@@ -188,8 +191,10 @@ class C(builtins.object)
is_it_true(self)
Return self.get_answer()
say_no(self)
+ ----------------------------------------------------------------------
Class methods defined here:
- __class_getitem__(item) from builtins.type
+ __class_getitem__(item)
+ ----------------------------------------------------------------------
Data descriptors defined here:
__dict__
dictionary for instance variables
@@ -327,6 +332,10 @@ def get_pydoc_html(module):
loc = "
Module Docs"
return output.strip(), loc
+def clean_text(doc):
+ # clean up the extra text formatting that pydoc performs
+ return re.sub('\b.', '', doc)
+
def get_pydoc_link(module):
"Returns a documentation web link of a module"
abspath = os.path.abspath
@@ -344,10 +353,7 @@ def get_pydoc_text(module):
loc = "\nMODULE DOCS\n " + loc + "\n"
output = doc.docmodule(module)
-
- # clean up the extra text formatting that pydoc performs
- patt = re.compile('\b.')
- output = patt.sub('', output)
+ output = clean_text(output)
return output.strip(), loc
def get_html_title(text):
@@ -364,6 +370,7 @@ def html2text(html):
Tailored for pydoc tests only.
"""
html = html.replace("", "\n")
+ html = html.replace("
", "-"*70)
html = re.sub("<.*?>", "", html)
html = pydoc.replace(html, " ", " ", ">", ">", "<", "<")
return html
@@ -791,8 +798,7 @@ def itemconfigure(self, tagOrId, cnf=None, **kw):
b_size = A.a_size
doc = pydoc.render_doc(B)
- # clean up the extra text formatting that pydoc performs
- doc = re.sub('\b.', '', doc)
+ doc = clean_text(doc)
self.assertEqual(doc, '''\
Python Library Documentation: class B in module %s
@@ -1147,7 +1153,7 @@ def test_unbound_python_method(self):
@requires_docstrings
def test_unbound_builtin_method(self):
self.assertEqual(self._get_summary_line(_pickle.Pickler.dump),
- "dump(self, obj, /)")
+ "dump(self, obj, /) unbound _pickle.Pickler method")
# these no longer include "self"
def test_bound_python_method(self):
@@ -1183,6 +1189,14 @@ def test_module_level_callable(self):
self.assertEqual(self._get_summary_line(os.stat),
"stat(path, *, dir_fd=None, follow_symlinks=True)")
+ def test_unbound_builtin_method_noargs(self):
+ self.assertEqual(self._get_summary_line(str.lower),
+ "lower(self, /) unbound builtins.str method")
+
+ def test_bound_builtin_method_noargs(self):
+ self.assertEqual(self._get_summary_line(''.lower),
+ "lower() method of builtins.str instance")
+
@requires_docstrings
def test_staticmethod(self):
class X:
@@ -1215,13 +1229,13 @@ def cm(cls, x):
'cm(...)\n'
' A class method\n')
self.assertEqual(self._get_summary_lines(X.cm), """\
-cm(x) method of builtins.type instance
+cm(x) class method of test.test_pydoc.X
A class method
""")
self.assertIn("""
| Class methods defined here:
|\x20\x20
- | cm(x) from builtins.type
+ | cm(x)
| A class method
""", pydoc.plain(pydoc.render_doc(X)))
@@ -1378,6 +1392,128 @@ def a_fn_with_https_link():
)
+class PydocFodderTest(unittest.TestCase):
+
+ def getsection(self, text, beginline, endline):
+ lines = text.splitlines()
+ beginindex, endindex = 0, None
+ if beginline is not None:
+ beginindex = lines.index(beginline)
+ if endline is not None:
+ endindex = lines.index(endline, beginindex)
+ return lines[beginindex:endindex]
+
+ def test_text_doc_routines_in_class(self, cls=pydocfodder.B):
+ doc = pydoc.TextDoc()
+ result = doc.docclass(cls)
+ result = clean_text(result)
+ where = 'defined here' if cls is pydocfodder.B else 'inherited from B'
+ lines = self.getsection(result, f' | Methods {where}:', ' | ' + '-'*70)
+ self.assertIn(' | A_method_alias = A_method(self)', lines)
+ self.assertIn(' | B_method_alias = B_method(self)', lines)
+ self.assertIn(' | A_staticmethod(x, y) from test.pydocfodder.A', lines)
+ self.assertIn(' | A_staticmethod_alias = A_staticmethod(x, y)', lines)
+ self.assertIn(' | global_func(x, y) from test.pydocfodder', lines)
+ self.assertIn(' | global_func_alias = global_func(x, y)', lines)
+ self.assertIn(' | global_func2_alias = global_func2(x, y) from test.pydocfodder', lines)
+ self.assertIn(' | __repr__(self, /) from builtins.object', lines)
+ self.assertIn(' | object_repr = __repr__(self, /)', lines)
+
+ lines = self.getsection(result, f' | Static methods {where}:', ' | ' + '-'*70)
+ self.assertIn(' | A_classmethod_ref = A_classmethod(x) class method of test.pydocfodder.A', lines)
+ note = '' if cls is pydocfodder.B else ' class method of test.pydocfodder.B'
+ self.assertIn(' | B_classmethod_ref = B_classmethod(x)' + note, lines)
+ self.assertIn(' | A_method_ref = A_method() method of test.pydocfodder.A instance', lines)
+ self.assertIn(' | get(key, default=None, /) method of builtins.dict instance', lines)
+ self.assertIn(' | dict_get = get(key, default=None, /) method of builtins.dict instance', lines)
+
+ lines = self.getsection(result, f' | Class methods {where}:', ' | ' + '-'*70)
+ self.assertIn(' | B_classmethod(x)', lines)
+ self.assertIn(' | B_classmethod_alias = B_classmethod(x)', lines)
+
+ def test_html_doc_routines_in_class(self, cls=pydocfodder.B):
+ doc = pydoc.HTMLDoc()
+ result = doc.docclass(cls)
+ result = html2text(result)
+ where = 'defined here' if cls is pydocfodder.B else 'inherited from B'
+ lines = self.getsection(result, f'Methods {where}:', '-'*70)
+ self.assertIn('A_method_alias = A_method(self)', lines)
+ self.assertIn('B_method_alias = B_method(self)', lines)
+ self.assertIn('A_staticmethod(x, y) from test.pydocfodder.A', lines)
+ self.assertIn('A_staticmethod_alias = A_staticmethod(x, y)', lines)
+ self.assertIn('global_func(x, y) from test.pydocfodder', lines)
+ self.assertIn('global_func_alias = global_func(x, y)', lines)
+ self.assertIn('global_func2_alias = global_func2(x, y) from test.pydocfodder', lines)
+ self.assertIn('__repr__(self, /) from builtins.object', lines)
+ self.assertIn('object_repr = __repr__(self, /)', lines)
+
+ lines = self.getsection(result, f'Static methods {where}:', '-'*70)
+ self.assertIn('A_classmethod_ref = A_classmethod(x) class method of test.pydocfodder.A', lines)
+ note = '' if cls is pydocfodder.B else ' class method of test.pydocfodder.B'
+ self.assertIn('B_classmethod_ref = B_classmethod(x)' + note, lines)
+ self.assertIn('A_method_ref = A_method() method of test.pydocfodder.A instance', lines)
+
+ lines = self.getsection(result, f'Class methods {where}:', '-'*70)
+ self.assertIn('B_classmethod(x)', lines)
+ self.assertIn('B_classmethod_alias = B_classmethod(x)', lines)
+
+ def test_text_doc_inherited_routines_in_class(self):
+ self.test_text_doc_routines_in_class(pydocfodder.D)
+
+ def test_html_doc_inherited_routines_in_class(self):
+ self.test_html_doc_routines_in_class(pydocfodder.D)
+
+ def test_text_doc_routines_in_module(self):
+ doc = pydoc.TextDoc()
+ result = doc.docmodule(pydocfodder)
+ result = clean_text(result)
+ lines = self.getsection(result, 'FUNCTIONS', 'FILE')
+ # function alias
+ self.assertIn(' global_func_alias = global_func(x, y)', lines)
+ self.assertIn(' A_staticmethod(x, y)', lines)
+ self.assertIn(' A_staticmethod_alias = A_staticmethod(x, y)', lines)
+ # bound class methods
+ self.assertIn(' A_classmethod(x) class method of A', lines)
+ self.assertIn(' A_classmethod2 = A_classmethod(x) class method of A', lines)
+ self.assertIn(' A_classmethod3 = A_classmethod(x) class method of B', lines)
+ # bound methods
+ self.assertIn(' A_method() method of A instance', lines)
+ self.assertIn(' A_method2 = A_method() method of A instance', lines)
+ self.assertIn(' A_method3 = A_method() method of B instance', lines)
+ self.assertIn(' A_staticmethod_ref = A_staticmethod(x, y)', lines)
+ self.assertIn(' A_staticmethod_ref2 = A_staticmethod(y) method of B instance', lines)
+ self.assertIn(' get(key, default=None, /) method of builtins.dict instance', lines)
+ self.assertIn(' dict_get = get(key, default=None, /) method of builtins.dict instance', lines)
+ # unbound methods
+ self.assertIn(' B_method(self)', lines)
+ self.assertIn(' B_method2 = B_method(self)', lines)
+
+ def test_html_doc_routines_in_module(self):
+ doc = pydoc.HTMLDoc()
+ result = doc.docmodule(pydocfodder)
+ result = html2text(result)
+ lines = self.getsection(result, ' Functions', None)
+ # function alias
+ self.assertIn(' global_func_alias = global_func(x, y)', lines)
+ self.assertIn(' A_staticmethod(x, y)', lines)
+ self.assertIn(' A_staticmethod_alias = A_staticmethod(x, y)', lines)
+ # bound class methods
+ self.assertIn('A_classmethod(x) class method of A', lines)
+ self.assertIn(' A_classmethod2 = A_classmethod(x) class method of A', lines)
+ self.assertIn(' A_classmethod3 = A_classmethod(x) class method of B', lines)
+ # bound methods
+ self.assertIn(' A_method() method of A instance', lines)
+ self.assertIn(' A_method2 = A_method() method of A instance', lines)
+ self.assertIn(' A_method3 = A_method() method of B instance', lines)
+ self.assertIn(' A_staticmethod_ref = A_staticmethod(x, y)', lines)
+ self.assertIn(' A_staticmethod_ref2 = A_staticmethod(y) method of B instance', lines)
+ self.assertIn(' get(key, default=None, /) method of builtins.dict instance', lines)
+ self.assertIn(' dict_get = get(key, default=None, /) method of builtins.dict instance', lines)
+ # unbound methods
+ self.assertIn(' B_method(self)', lines)
+ self.assertIn(' B_method2 = B_method(self)', lines)
+
+
@unittest.skipIf(
is_emscripten or is_wasi,
"Socket server not available on Emscripten/WASI."
diff --git a/Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst b/Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst
new file mode 100644
index 00000000000000..a317271947dc37
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-01-11-15-10-53.gh-issue-97959.UOj6d4.rst
@@ -0,0 +1,7 @@
+Fix rendering class methods, bound methods, method and function aliases in
+:mod:`pydoc`. Class methods no longer have "method of builtins.type
+instance" note. Corresponding notes are now added for class and unbound
+methods. Method and function aliases now have references to the module or
+the class where the origin was defined if it differs from the current. Bound
+methods are now listed in the static methods section. Methods of builtin
+classes are now supported as well as methods of Python classes.