diff --git a/bpython/inspection.py b/bpython/inspection.py index 10b7db761..49423e45a 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -250,17 +250,16 @@ def getfuncprops(func, f): return None try: if py3: - argspec = inspect.getfullargspec(f) + argspec = get_argspec_from_signature(f) else: - argspec = inspect.getargspec(f) + argspec = list(inspect.getargspec(f)) - argspec = list(argspec) fixlongargs(f, argspec) if len(argspec) == 4: argspec = argspec + [list(), dict(), None] argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) - except (TypeError, KeyError): + except (TypeError, KeyError, ValueError): with AttrCleaner(f): argspec = getpydocspec(f, func) if argspec is None: @@ -284,6 +283,55 @@ def is_callable(obj): return callable(obj) +def get_argspec_from_signature(f): + """Get callable signature from inspect.signature in argspec format. + + inspect.signature is a Python 3 only function that returns the signature of + a function. Its advantage over inspect.getfullargspec is that it returns + the signature of a decorated function, if the wrapper function itself is + decorated with functools.wraps. + + """ + args = [] + varargs = varkwargs = None + defaults = [] + kwonly = [] + kwonly_defaults = {} + annotations = {} + + signature = inspect.signature(f) + for parameter in signature.parameters.values(): + if parameter.annotation is not inspect._empty: + annotations[parameter.name] = parameter.annotation + + if parameter.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: + args.append(parameter.name) + if parameter.default is not inspect._empty: + defaults.append(parameter.default) + elif parameter.kind == inspect.Parameter.POSITIONAL_ONLY: + args.append(parameter.name) + elif parameter.kind == inspect.Parameter.VAR_POSITIONAL: + varargs = parameter.name + elif parameter.kind == inspect.Parameter.KEYWORD_ONLY: + kwonly.append(parameter.name) + kwonly_defaults[parameter.name] = parameter.default + elif parameter.kind == inspect.Parameter.VAR_KEYWORD: + varkwargs = parameter.name + + # inspect.getfullargspec returns None for 'defaults', 'kwonly_defaults' and + # 'annotations' if there are no values for them. + if not defaults: + defaults = None + + if not kwonly_defaults: + kwonly_defaults = None + + if not annotations: + annotations = None + + return [args, varargs, varkwargs, defaults, kwonly, kwonly_defaults, annotations] + + get_encoding_line_re = LazyReCompile(r'^.*coding[:=]\s*([-\w.]+).*$')