commit 5e154e332c86109f3a4df280b75650197b2d29bd Author: Yury Selivanov Date: Sat Jan 11 17:25:48 2014 -0500 wip diff --git a/Lib/inspect.py b/Lib/inspect.py index 781a532..ceb7dea 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -933,7 +933,7 @@ FullArgSpec = namedtuple('FullArgSpec', 'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations') def getfullargspec(func): - """Get the names and default values of a function's arguments. + """Get the names and default values of a callable object's arguments. A tuple of seven things is returned: (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults annotations). @@ -949,11 +949,61 @@ def getfullargspec(func): if ismethod(func): func = func.__func__ - if not isfunction(func): - raise TypeError('{!r} is not a Python function'.format(func)) - args, varargs, kwonlyargs, varkw = _getfullargs(func.__code__) - return FullArgSpec(args, varargs, varkw, func.__defaults__, - kwonlyargs, func.__kwdefaults__, func.__annotations__) + + try: + sig = signature(func) + except Exception as ex: + # Most of the times 'signature' will raise ValueError. + # But, it can also raise AttributeError, and, maybe something + # else. So to be fully backwards compatible, we catch all + # exceptions here, and reraise a TypeError. + raise TypeError('unsupported callable') from ex + + args = [] + varargs = None + varkw = None + kwonlyargs = [] + defaults = () + annotations = {} + defaults = () + kwdefaults = {} + + if sig.return_annotation is not sig.empty: + annotations['return'] = sig.return_annotation + + for param in sig.parameters.values(): + kind = param.kind + name = param.name + + if kind is _POSITIONAL_ONLY: + args.append(name) + elif kind is _POSITIONAL_OR_KEYWORD: + args.append(name) + if param.default is not param.empty: + defaults += (param.default,) + elif kind is _VAR_POSITIONAL: + varargs = name + elif kind is _KEYWORD_ONLY: + kwonlyargs.append(name) + if param.default is not param.empty: + kwdefaults[name] = param.default + elif kind is _VAR_KEYWORD: + varkw = name + + if param.annotation is not param.empty: + annotations[name] = param.annotation + + if not kwdefaults: + # compatibility with 'func.__kwdefaults__' + kwdefaults = None + + if not defaults: + # compatibility with 'func.__defaults__' + defaults = None + + return FullArgSpec(args, varargs, varkw, defaults, + kwonlyargs, kwdefaults, annotations) + ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals') diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 028eeb9..0283453 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -568,6 +568,25 @@ class TestClassesAndFunctions(unittest.TestCase): kwonlyargs_e=['arg'], formatted='(*, arg)') + def test_getfullargspec_signature_attr(self): + def test(): + pass + spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY) + test.__signature__ = inspect.Signature(parameters=(spam_param,)) + + self.assertFullArgSpecEquals(test, args_e=['spam'], formatted='(spam)') + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_getfullagrspec_builtin_func(self): + builtin = _testcapi.docstring_with_signature_with_defaults + spec = inspect.getfullargspec(builtin) + self.assertEqual(spec.defaults[0], 'avocado') + + def test_getfullagrspec_builtin_func_no_signature(self): + builtin = _testcapi.docstring_no_signature + with self.assertRaises(TypeError): + inspect.getfullargspec(builtin) def test_getargspec_method(self): class A(object):