From e6d607413793aff9eedee2f8e9ce7d3d7b42f109 Mon Sep 17 00:00:00 2001 From: Benedikt Rascher-Friesenhausen Date: Sat, 16 Feb 2019 11:07:51 +0100 Subject: [PATCH 1/3] Display the correct signature for a decorated function in Python 3 This patch changes it so that `inspect.signature` is used instead of `inspect.getfullargspec` to get a function's signature, when using Python 3. Python 3.3 introduced the `inspect.signature` function as a new way to get the signature of a function (as an alternative to `inspect.getargspec` and `inspect.getfullargspec`). `inspect.signature` has the advantage that it preserves the signature of a decorated function if `functools.wraps` is used to decorated the wrapper function. Having a function's signature available is very hepful, especially when testing things out in a REPL. --- bpython/inspection.py | 54 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 10b7db761..5817fac08 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -250,11 +250,10 @@ 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] @@ -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._ParameterKind.POSITIONAL_OR_KEYWORD: + args.append(parameter.name) + if parameter.default is not inspect._empty: + defaults.append(parameter.default) + elif parameter.kind == inspect._ParameterKind.POSITIONAL_ONLY: + args.append(parameter.name) + elif parameter.kind == inspect._ParameterKind.VAR_POSITIONAL: + varargs = parameter.name + elif parameter.kind == inspect._ParameterKind.KEYWORD_ONLY: + kwonly.append(parameter.name) + kwonly_defaults[parameter.name] = parameter.default + elif parameter.kind == inspect._ParameterKind.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.]+).*$') From 2713669896fdfd8bdfeec08b406c0905de12ca99 Mon Sep 17 00:00:00 2001 From: Benedikt Rascher-Friesenhausen Date: Sat, 16 Feb 2019 11:45:37 +0100 Subject: [PATCH 2/3] Correctly reference inspect parameter kinds Instead of referencing parameter kinds as class attributes on the private `_ParameterKind` class we reference them on the public `Parameter` class. This has two advantages: 1) We don't use a private interface. 2) The class attributes on `_ParameterKind` have only been added in Python 3.5, but on `Parameter` they have existed since Python 3.3. --- bpython/inspection.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 5817fac08..410ac3173 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -304,18 +304,18 @@ def get_argspec_from_signature(f): if parameter.annotation is not inspect._empty: annotations[parameter.name] = parameter.annotation - if parameter.kind == inspect._ParameterKind.POSITIONAL_OR_KEYWORD: + 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._ParameterKind.POSITIONAL_ONLY: + elif parameter.kind == inspect.Parameter.POSITIONAL_ONLY: args.append(parameter.name) - elif parameter.kind == inspect._ParameterKind.VAR_POSITIONAL: + elif parameter.kind == inspect.Parameter.VAR_POSITIONAL: varargs = parameter.name - elif parameter.kind == inspect._ParameterKind.KEYWORD_ONLY: + elif parameter.kind == inspect.Parameter.KEYWORD_ONLY: kwonly.append(parameter.name) kwonly_defaults[parameter.name] = parameter.default - elif parameter.kind == inspect._ParameterKind.VAR_KEYWORD: + elif parameter.kind == inspect.Parameter.VAR_KEYWORD: varkwargs = parameter.name # inspect.getfullargspec returns None for 'defaults', 'kwonly_defaults' and From 26afdd9eeee65f0633e7f1752ed31ec9d62dd73a Mon Sep 17 00:00:00 2001 From: Benedikt Rascher-Friesenhausen Date: Sat, 16 Feb 2019 11:50:02 +0100 Subject: [PATCH 3/3] Catch function signature inspection errors for built-in types Some built-in functions (e.g. `map`) can't be inspected with `inspect.getargspec`, `inspect.getfullargspec` or `inspect.signature`. The exceptions from `inspect.getargspec` and `inspect.getfullargspec` are all caught in the code, but `inspect.signature` raises a `ValueError` instead of a `TypeError`. This exception is now also caught. --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 410ac3173..49423e45a 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -259,7 +259,7 @@ def getfuncprops(func, f): 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: