diff --git a/Lib/inspect.py b/Lib/inspect.py --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1572,13 +1572,19 @@ _partial_kwarg=_partial_kwarg) def __str__(self): + return self.render() + + def render(self, + formatvalue=lambda value: '=' +repr(value), + formatpositional=lambda value: '<{}>'.format(value), + formatannotation=formatannotation): kind = self.kind formatted = self._name if kind == _POSITIONAL_ONLY: if formatted is None: formatted = '' - formatted = '<{}>'.format(formatted) + formatted = formatpositional(formatted) # Add annotation and default value if self._annotation is not _empty: @@ -1586,7 +1592,7 @@ formatannotation(self._annotation)) if self._default is not _empty: - formatted = '{}={}'.format(formatted, repr(self._default)) + formatted = '{}{}'.format(formatted, formatvalue(self._default)) if kind == _VAR_POSITIONAL: formatted = '*' + formatted @@ -2043,10 +2049,18 @@ return __bind_self._bind(args, kwargs, partial=True) def __str__(self): + return self.render() + + def render(self, + formatvalue=lambda value:'=' + str(value), + formatannotation=formatannotation, + formatpositional=lambda value: '<{}>'.format(value), + islambda=False): + result = [] render_kw_only_separator = True for idx, param in enumerate(self.parameters.values()): - formatted = str(param) + formatted = param.render(formatvalue, formatpositional, formatannotation) kind = param.kind if kind == _VAR_POSITIONAL: @@ -2070,4 +2084,7 @@ anno = formatannotation(self.return_annotation) rendered += ' -> {}'.format(anno) + elif islambda: + rendered = rendered[1:-1] + return rendered diff --git a/Lib/pydoc.py b/Lib/pydoc.py --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -913,21 +913,18 @@ reallink = realname title = '%s = %s' % ( anchor, name, reallink) - if inspect.isfunction(object): - args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann = \ - inspect.getfullargspec(object) - argspec = inspect.formatargspec( - args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann, - formatvalue=self.formatvalue, - formatannotation=inspect.formatannotationrelativeto(object)) - if realname == '': - title = '%s lambda ' % 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 - else: - argspec = '(...)' + + try: + signature = inspect.signature(object) + except ValueError: + signature = '(...)' + argspec = signature.render(formatvalue=self.formatvalue, + formatannotation=inspect.formatannotationrelativeto(object), + formatpositional=lambda nm: '<{}>'.format(nm), + islambda=(inspect.isfunction(object) and realname == '')) + if inspect.isfunction(object) and realname == '': + title = '%s lambda ' % name + decl = title + argspec + (note and self.grey( '%s' % note)) @@ -1309,21 +1306,18 @@ cl.__dict__[realname] is object): skipdocs = 1 title = self.bold(name) + ' = ' + realname - if inspect.isfunction(object): - args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann = \ - inspect.getfullargspec(object) - argspec = inspect.formatargspec( - args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann, - formatvalue=self.formatvalue, - formatannotation=inspect.formatannotationrelativeto(object)) - if realname == '': - 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 - else: + + try: + signature = inspect.signature(object) + argspec = signature.render(formatvalue=self.formatvalue, + formatannotation=inspect.formatannotationrelativeto(object), + islambda=(inspect.isfunction(object) and realname == '')) + except ValueError: argspec = '(...)' + if inspect.isfunction(object) and realname == '': + title = title + ' ' + + decl = title + argspec + note if skipdocs: diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -395,6 +395,36 @@ synopsis = pydoc.synopsis(TESTFN, {}) self.assertEqual(synopsis, 'line 1: h\xe9') + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_function_like_signature(self): + class Function: + def __init__(self): + self._func = lambda a=3, b=4: a*b + + def __call__(self, *args, **kwds): + return self._func(*args, **kwds) + + @property + def __name__(self): + return '$function$' + + @property + def __signature__(self): + return inspect.signature(self._func) + + def __get__(self, a, b): + return self + + function = Function() + self.assertEqual(function(), 12) + + doc = pydoc.TextDoc() + result = doc.document(function) + + self.maxDiff = None + self.assertEqual(result, '''$\x08$f\x08fu\x08un\x08nc\x08ct\x08ti\x08io\x08on\x08n$\x08$(a=3, b=4)\n''') + class PydocImportTest(unittest.TestCase):