summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorLarry Hastings <larry@hastings.org>2013-11-23 23:37:55 (GMT)
committerLarry Hastings <larry@hastings.org>2013-11-23 23:37:55 (GMT)
commit44e2eaab5491881120aab43e2838da8afe7ab70e (patch)
tree92847876fa89736ab40d027431ff27e4973409c9 /Lib
parent7fa6e1aeea111e7d954b753fb483afc18f21add0 (diff)
downloadcpython-44e2eaab5491881120aab43e2838da8afe7ab70e.zip
cpython-44e2eaab5491881120aab43e2838da8afe7ab70e.tar.gz
cpython-44e2eaab5491881120aab43e2838da8afe7ab70e.tar.bz2
Issue #19674: inspect.signature() now produces a correct signature
for some builtins.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/inspect.py62
-rwxr-xr-xLib/pydoc.py52
-rw-r--r--Lib/test/test_capi.py29
-rw-r--r--Lib/test/test_inspect.py7
4 files changed, 118 insertions, 32 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index edbf927..dc94e44 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -31,6 +31,7 @@ Here are some of the useful functions provided by this module:
__author__ = ('Ka-Ping Yee <ping@lfw.org>',
'Yury Selivanov <yselivanov@sprymix.com>')
+import ast
import importlib.machinery
import itertools
import linecache
@@ -1461,6 +1462,9 @@ def signature(obj):
if isinstance(obj, types.FunctionType):
return Signature.from_function(obj)
+ if isinstance(obj, types.BuiltinFunctionType):
+ return Signature.from_builtin(obj)
+
if isinstance(obj, functools.partial):
sig = signature(obj.func)
@@ -1942,6 +1946,64 @@ class Signature:
return_annotation=annotations.get('return', _empty),
__validate_parameters__=False)
+ @classmethod
+ def from_builtin(cls, func):
+ s = getattr(func, "__text_signature__", None)
+ if not s:
+ return None
+
+ if s.endswith("/)"):
+ kind = Parameter.POSITIONAL_ONLY
+ s = s[:-2] + ')'
+ else:
+ kind = Parameter.POSITIONAL_OR_KEYWORD
+
+ s = "def foo" + s + ": pass"
+
+ try:
+ module = ast.parse(s)
+ except SyntaxError:
+ return None
+ if not isinstance(module, ast.Module):
+ return None
+
+ # ast.FunctionDef
+ f = module.body[0]
+
+ parameters = []
+ empty = Parameter.empty
+
+ def p(name_node, default_node, default=empty):
+ name = name_node.arg
+
+ if isinstance(default_node, ast.Num):
+ default = default.n
+ elif isinstance(default_node, ast.NameConstant):
+ default = default_node.value
+ parameters.append(Parameter(name, kind, default=default, annotation=empty))
+
+ # non-keyword-only parameters
+ for name, default in reversed(list(itertools.zip_longest(reversed(f.args.args), reversed(f.args.defaults), fillvalue=None))):
+ p(name, default)
+
+ # *args
+ if f.args.vararg:
+ kind = Parameter.VAR_POSITIONAL
+ p(f.args.vararg, empty)
+
+ # keyword-only arguments
+ kind = Parameter.KEYWORD_ONLY
+ for name, default in zip(f.args.kwonlyargs, f.args.kw_defaults):
+ p(name, default)
+
+ # **kwargs
+ if f.args.kwarg:
+ kind = Parameter.VAR_KEYWORD
+ p(f.args.kwarg, empty)
+
+ return cls(parameters, return_annotation=cls.empty)
+
+
@property
def parameters(self):
return self._parameters
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index e8127a2..2e63226 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -916,20 +916,18 @@ class HTMLDoc(Doc):
reallink = realname
title = '<a name="%s"><strong>%s</strong></a> = %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 == '<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
- else:
+ argspec = None
+ if inspect.isfunction(object) or inspect.isbuiltin(object):
+ signature = inspect.signature(object)
+ 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
+ if not argspec:
argspec = '(...)'
decl = title + argspec + (note and self.grey(
@@ -1313,20 +1311,18 @@ location listed above.
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 == '<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
- else:
+ argspec = None
+ if inspect.isfunction(object) or inspect.isbuiltin(object):
+ signature = inspect.signature(object)
+ 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
+ if not argspec:
argspec = '(...)'
decl = title + argspec + note
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 000079e..6f75b77 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -109,6 +109,35 @@ class CAPITest(unittest.TestCase):
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
Z(),[b'1'],3,[1, 2],5,6,7,8,9,10,11,12,13,14,15,16,17)
+ def test_docstring_signature_parsing(self):
+
+ self.assertEqual(_testcapi.no_docstring.__doc__, None)
+ self.assertEqual(_testcapi.no_docstring.__text_signature__, None)
+
+ self.assertEqual(_testcapi.docstring_empty.__doc__, "")
+ self.assertEqual(_testcapi.docstring_empty.__text_signature__, None)
+
+ self.assertEqual(_testcapi.docstring_no_signature.__doc__,
+ "This docstring has no signature.")
+ self.assertEqual(_testcapi.docstring_no_signature.__text_signature__, None)
+
+ self.assertEqual(_testcapi.docstring_with_invalid_signature.__doc__,
+ "docstring_with_invalid_signature (boo)\n"
+ "\n"
+ "This docstring has an invalid signature."
+ )
+ self.assertEqual(_testcapi.docstring_with_invalid_signature.__text_signature__, None)
+
+ self.assertEqual(_testcapi.docstring_with_signature.__doc__,
+ "This docstring has a valid signature.")
+ self.assertEqual(_testcapi.docstring_with_signature.__text_signature__, "(sig)")
+
+ self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__doc__,
+ "This docstring has a valid signature and some extra newlines.")
+ self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__text_signature__,
+ "(parameter)")
+
+
@unittest.skipUnless(threading, 'Threading required for this test.')
class TestPendingCalls(unittest.TestCase):
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 9d34904..0258d3df 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -1588,10 +1588,9 @@ class TestSignatureObject(unittest.TestCase):
with self.assertRaisesRegex(ValueError, 'not supported by signature'):
# support for 'method-wrapper'
inspect.signature(min.__call__)
- with self.assertRaisesRegex(ValueError,
- 'no signature found for builtin function'):
- # support for 'method-wrapper'
- inspect.signature(min)
+ self.assertEqual(inspect.signature(min), None)
+ signature = inspect.signature(os.stat)
+ self.assertTrue(isinstance(signature, inspect.Signature))
def test_signature_on_non_function(self):
with self.assertRaisesRegex(TypeError, 'is not a callable object'):