mirror of https://github.com/python/cpython
gh-107782: Pydoc: fall back to __text_signature__ if inspect.signature() fails (GH-107786)
It allows to show signatures which are not representable in Python, e.g. for getattr and dict.pop.
This commit is contained in:
parent
5f7d4ecf30
commit
a39f0a3506
78
Lib/pydoc.py
78
Lib/pydoc.py
|
@ -197,6 +197,24 @@ def splitdoc(doc):
|
||||||
return lines[0], '\n'.join(lines[2:])
|
return lines[0], '\n'.join(lines[2:])
|
||||||
return '', '\n'.join(lines)
|
return '', '\n'.join(lines)
|
||||||
|
|
||||||
|
def _getargspec(object):
|
||||||
|
try:
|
||||||
|
signature = inspect.signature(object)
|
||||||
|
if signature:
|
||||||
|
return str(signature)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
argspec = getattr(object, '__text_signature__', None)
|
||||||
|
if argspec:
|
||||||
|
if argspec[:2] == '($':
|
||||||
|
argspec = '(' + argspec[2:]
|
||||||
|
if getattr(object, '__self__', None) is not None:
|
||||||
|
# Strip the bound argument.
|
||||||
|
m = re.match(r'\(\w+(?:(?=\))|,\s*(?:/(?:(?=\))|,\s*))?)', argspec)
|
||||||
|
if m:
|
||||||
|
argspec = '(' + argspec[m.end():]
|
||||||
|
return argspec
|
||||||
|
return None
|
||||||
|
|
||||||
def classname(object, modname):
|
def classname(object, modname):
|
||||||
"""Get a class name and qualify it with a module name if necessary."""
|
"""Get a class name and qualify it with a module name if necessary."""
|
||||||
name = object.__name__
|
name = object.__name__
|
||||||
|
@ -1003,14 +1021,9 @@ class HTMLDoc(Doc):
|
||||||
title = title + '(%s)' % ', '.join(parents)
|
title = title + '(%s)' % ', '.join(parents)
|
||||||
|
|
||||||
decl = ''
|
decl = ''
|
||||||
try:
|
argspec = _getargspec(object)
|
||||||
signature = inspect.signature(object)
|
if argspec and argspec != '()':
|
||||||
except (ValueError, TypeError):
|
decl = name + self.escape(argspec) + '\n\n'
|
||||||
signature = None
|
|
||||||
if signature:
|
|
||||||
argspec = str(signature)
|
|
||||||
if argspec and argspec != '()':
|
|
||||||
decl = name + self.escape(argspec) + '\n\n'
|
|
||||||
|
|
||||||
doc = getdoc(object)
|
doc = getdoc(object)
|
||||||
if decl:
|
if decl:
|
||||||
|
@ -1063,18 +1076,13 @@ class HTMLDoc(Doc):
|
||||||
anchor, name, reallink)
|
anchor, name, reallink)
|
||||||
argspec = None
|
argspec = None
|
||||||
if inspect.isroutine(object):
|
if inspect.isroutine(object):
|
||||||
try:
|
argspec = _getargspec(object)
|
||||||
signature = inspect.signature(object)
|
if argspec and realname == '<lambda>':
|
||||||
except (ValueError, TypeError):
|
title = '<strong>%s</strong> <em>lambda</em> ' % name
|
||||||
signature = None
|
# XXX lambda's won't usually have func_annotations['return']
|
||||||
if signature:
|
# since the syntax doesn't support but it is possible.
|
||||||
argspec = str(signature)
|
# So removing parentheses isn't truly safe.
|
||||||
if realname == '<lambda>':
|
argspec = argspec[1:-1] # remove parentheses
|
||||||
title = '<strong>%s</strong> <em>lambda</em> ' % name
|
|
||||||
# XXX lambda's won't usually have func_annotations['return']
|
|
||||||
# since the syntax doesn't support but it is possible.
|
|
||||||
# So removing parentheses isn't truly safe.
|
|
||||||
argspec = argspec[1:-1] # remove parentheses
|
|
||||||
if not argspec:
|
if not argspec:
|
||||||
argspec = '(...)'
|
argspec = '(...)'
|
||||||
|
|
||||||
|
@ -1321,14 +1329,9 @@ location listed above.
|
||||||
contents = []
|
contents = []
|
||||||
push = contents.append
|
push = contents.append
|
||||||
|
|
||||||
try:
|
argspec = _getargspec(object)
|
||||||
signature = inspect.signature(object)
|
if argspec and argspec != '()':
|
||||||
except (ValueError, TypeError):
|
push(name + argspec + '\n')
|
||||||
signature = None
|
|
||||||
if signature:
|
|
||||||
argspec = str(signature)
|
|
||||||
if argspec and argspec != '()':
|
|
||||||
push(name + argspec + '\n')
|
|
||||||
|
|
||||||
doc = getdoc(object)
|
doc = getdoc(object)
|
||||||
if doc:
|
if doc:
|
||||||
|
@ -1492,18 +1495,13 @@ location listed above.
|
||||||
argspec = None
|
argspec = None
|
||||||
|
|
||||||
if inspect.isroutine(object):
|
if inspect.isroutine(object):
|
||||||
try:
|
argspec = _getargspec(object)
|
||||||
signature = inspect.signature(object)
|
if argspec and realname == '<lambda>':
|
||||||
except (ValueError, TypeError):
|
title = self.bold(name) + ' lambda '
|
||||||
signature = None
|
# XXX lambda's won't usually have func_annotations['return']
|
||||||
if signature:
|
# since the syntax doesn't support but it is possible.
|
||||||
argspec = str(signature)
|
# So removing parentheses isn't truly safe.
|
||||||
if realname == '<lambda>':
|
argspec = argspec[1:-1] # remove parentheses
|
||||||
title = self.bold(name) + ' lambda '
|
|
||||||
# XXX lambda's won't usually have func_annotations['return']
|
|
||||||
# since the syntax doesn't support but it is possible.
|
|
||||||
# So removing parentheses isn't truly safe.
|
|
||||||
argspec = argspec[1:-1] # remove parentheses
|
|
||||||
if not argspec:
|
if not argspec:
|
||||||
argspec = '(...)'
|
argspec = '(...)'
|
||||||
decl = asyncqualifier + title + argspec + note
|
decl = asyncqualifier + title + argspec + note
|
||||||
|
|
|
@ -1230,6 +1230,60 @@ class TestDescriptions(unittest.TestCase):
|
||||||
self.assertEqual(self._get_summary_line(dict.__class_getitem__),
|
self.assertEqual(self._get_summary_line(dict.__class_getitem__),
|
||||||
"__class_getitem__(object, /) method of builtins.type instance")
|
"__class_getitem__(object, /) method of builtins.type instance")
|
||||||
|
|
||||||
|
def test_module_level_callable_unrepresentable_default(self):
|
||||||
|
self.assertEqual(self._get_summary_line(getattr),
|
||||||
|
"getattr(object, name, default=<unrepresentable>, /)")
|
||||||
|
|
||||||
|
def test_builtin_staticmethod_unrepresentable_default(self):
|
||||||
|
self.assertEqual(self._get_summary_line(str.maketrans),
|
||||||
|
"maketrans(x, y=<unrepresentable>, z=<unrepresentable>, /)")
|
||||||
|
|
||||||
|
def test_unbound_builtin_method_unrepresentable_default(self):
|
||||||
|
self.assertEqual(self._get_summary_line(dict.pop),
|
||||||
|
"pop(self, key, default=<unrepresentable>, /)")
|
||||||
|
|
||||||
|
def test_bound_builtin_method_unrepresentable_default(self):
|
||||||
|
self.assertEqual(self._get_summary_line({}.pop),
|
||||||
|
"pop(key, default=<unrepresentable>, /) "
|
||||||
|
"method of builtins.dict instance")
|
||||||
|
|
||||||
|
def test_overridden_text_signature(self):
|
||||||
|
class C:
|
||||||
|
def meth(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
@classmethod
|
||||||
|
def cmeth(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
@staticmethod
|
||||||
|
def smeth(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
for text_signature, unbound, bound in [
|
||||||
|
("($slf)", "(slf, /)", "()"),
|
||||||
|
("($slf, /)", "(slf, /)", "()"),
|
||||||
|
("($slf, /, arg)", "(slf, /, arg)", "(arg)"),
|
||||||
|
("($slf, /, arg=<x>)", "(slf, /, arg=<x>)", "(arg=<x>)"),
|
||||||
|
("($slf, arg, /)", "(slf, arg, /)", "(arg, /)"),
|
||||||
|
("($slf, arg=<x>, /)", "(slf, arg=<x>, /)", "(arg=<x>, /)"),
|
||||||
|
("(/, slf, arg)", "(/, slf, arg)", "(/, slf, arg)"),
|
||||||
|
("(/, slf, arg=<x>)", "(/, slf, arg=<x>)", "(/, slf, arg=<x>)"),
|
||||||
|
("(slf, /, arg)", "(slf, /, arg)", "(arg)"),
|
||||||
|
("(slf, /, arg=<x>)", "(slf, /, arg=<x>)", "(arg=<x>)"),
|
||||||
|
("(slf, arg, /)", "(slf, arg, /)", "(arg, /)"),
|
||||||
|
("(slf, arg=<x>, /)", "(slf, arg=<x>, /)", "(arg=<x>, /)"),
|
||||||
|
]:
|
||||||
|
with self.subTest(text_signature):
|
||||||
|
C.meth.__text_signature__ = text_signature
|
||||||
|
self.assertEqual(self._get_summary_line(C.meth),
|
||||||
|
"meth" + unbound)
|
||||||
|
self.assertEqual(self._get_summary_line(C().meth),
|
||||||
|
"meth" + bound + " method of test.test_pydoc.C instance")
|
||||||
|
C.cmeth.__func__.__text_signature__ = text_signature
|
||||||
|
self.assertEqual(self._get_summary_line(C.cmeth),
|
||||||
|
"cmeth" + bound + " method of builtins.type instance")
|
||||||
|
C.smeth.__text_signature__ = text_signature
|
||||||
|
self.assertEqual(self._get_summary_line(C.smeth),
|
||||||
|
"smeth" + unbound)
|
||||||
|
|
||||||
@requires_docstrings
|
@requires_docstrings
|
||||||
def test_staticmethod(self):
|
def test_staticmethod(self):
|
||||||
class X:
|
class X:
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
:mod:`pydoc` is now able to show signatures which are not representable in
|
||||||
|
Python, e.g. for ``getattr`` and ``dict.pop``.
|
Loading…
Reference in New Issue