Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 38 additions & 40 deletions Lib/pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,24 @@ def splitdoc(doc):
return lines[0], '\n'.join(lines[2:])
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):
"""Get a class name and qualify it with a module name if necessary."""
name = object.__name__
Expand Down Expand Up @@ -1003,14 +1021,9 @@ def spilldata(msg, attrs, predicate):
title = title + '(%s)' % ', '.join(parents)

decl = ''
try:
signature = inspect.signature(object)
except (ValueError, TypeError):
signature = None
if signature:
argspec = str(signature)
if argspec and argspec != '()':
decl = name + self.escape(argspec) + '\n\n'
argspec = _getargspec(object)
if argspec and argspec != '()':
decl = name + self.escape(argspec) + '\n\n'

doc = getdoc(object)
if decl:
Expand Down Expand Up @@ -1063,18 +1076,13 @@ def docroutine(self, object, name=None, mod=None,
anchor, name, reallink)
argspec = None
if inspect.isroutine(object):
try:
signature = inspect.signature(object)
except (ValueError, TypeError):
signature = None
if signature:
argspec = str(signature)
if realname == '<lambda>':
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
argspec = _getargspec(object)
if argspec and realname == '<lambda>':
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:
argspec = '(...)'

Expand Down Expand Up @@ -1321,14 +1329,9 @@ def makename(c, m=object.__module__):
contents = []
push = contents.append

try:
signature = inspect.signature(object)
except (ValueError, TypeError):
signature = None
if signature:
argspec = str(signature)
if argspec and argspec != '()':
push(name + argspec + '\n')
argspec = _getargspec(object)
if argspec and argspec != '()':
push(name + argspec + '\n')

doc = getdoc(object)
if doc:
Expand Down Expand Up @@ -1492,18 +1495,13 @@ def docroutine(self, object, name=None, mod=None, cl=None):
argspec = None

if inspect.isroutine(object):
try:
signature = inspect.signature(object)
except (ValueError, TypeError):
signature = None
if signature:
argspec = str(signature)
if realname == '<lambda>':
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
argspec = _getargspec(object)
if argspec and realname == '<lambda>':
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:
argspec = '(...)'
decl = asyncqualifier + title + argspec + note
Expand Down
54 changes: 54 additions & 0 deletions Lib/test/test_pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,60 @@ def test_bound_builtin_classmethod_o(self):
self.assertEqual(self._get_summary_line(dict.__class_getitem__),
"__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
def test_staticmethod(self):
class X:
Expand Down
Original file line number Diff line number Diff line change
@@ -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``.