diff options
author | Yury Selivanov <yselivanov@sprymix.com> | 2014-01-28 17:26:24 (GMT) |
---|---|---|
committer | Yury Selivanov <yselivanov@sprymix.com> | 2014-01-28 17:26:24 (GMT) |
commit | 62560fb19a015181bce2fd5282a7688b5b1d61a4 (patch) | |
tree | c12c37b7bab44c0b41d7f312f559d687f28275c2 /Lib/inspect.py | |
parent | d65bc70db0326bfef18ac9fcda167d5eee49cb77 (diff) | |
download | cpython-62560fb19a015181bce2fd5282a7688b5b1d61a4.zip cpython-62560fb19a015181bce2fd5282a7688b5b1d61a4.tar.gz cpython-62560fb19a015181bce2fd5282a7688b5b1d61a4.tar.bz2 |
inspect.signature: Handle bound methods with '(*args)' signature correctly #20401
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r-- | Lib/inspect.py | 39 |
1 files changed, 34 insertions, 5 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py index 5f37a2a..41cbfe3 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1440,7 +1440,11 @@ def _get_user_defined_method(cls, method_name): return meth -def _get_partial_signature(wrapped_sig, partial, extra_args=()): +def _signature_get_partial(wrapped_sig, partial, extra_args=()): + # Internal helper to calculate how 'wrapped_sig' signature will + # look like after applying a 'functools.partial' object (or alike) + # on it. + new_params = OrderedDict(wrapped_sig.parameters.items()) partial_args = partial.args or () @@ -1485,6 +1489,31 @@ def _get_partial_signature(wrapped_sig, partial, extra_args=()): return wrapped_sig.replace(parameters=new_params.values()) +def _signature_bound_method(sig): + # Internal helper to transform signatures for unbound + # functions to bound methods + + params = tuple(sig.parameters.values()) + + if not params or params[0].kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + raise ValueError('invalid method signature') + + kind = params[0].kind + if kind in (_POSITIONAL_OR_KEYWORD, _POSITIONAL_ONLY): + # Drop first parameter: + # '(p1, p2[, ...])' -> '(p2[, ...])' + params = params[1:] + else: + if kind is not _VAR_POSITIONAL: + # Unless we add a new parameter type we never + # get here + raise ValueError('invalid argument type') + # It's a var-positional parameter. + # Do nothing. '(*args[, ...])' -> '(*args[, ...])' + + return sig.replace(parameters=params) + + def signature(obj): '''Get a signature object for the passed callable.''' @@ -1502,7 +1531,7 @@ def signature(obj): # In this case we skip the first parameter of the underlying # function (usually `self` or `cls`). sig = signature(obj.__func__) - return sig.replace(parameters=tuple(sig.parameters.values())[1:]) + return _signature_bound_method(sig) # Was this function wrapped by a decorator? obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__"))) @@ -1528,7 +1557,7 @@ def signature(obj): # automatically (as for boundmethods) wrapped_sig = signature(partialmethod.func) - sig = _get_partial_signature(wrapped_sig, partialmethod, (None,)) + sig = _signature_get_partial(wrapped_sig, partialmethod, (None,)) first_wrapped_param = tuple(wrapped_sig.parameters.values())[0] new_params = (first_wrapped_param,) + tuple(sig.parameters.values()) @@ -1540,7 +1569,7 @@ def signature(obj): if isinstance(obj, functools.partial): wrapped_sig = signature(obj.func) - return _get_partial_signature(wrapped_sig, obj) + return _signature_get_partial(wrapped_sig, obj) sig = None if isinstance(obj, type): @@ -1584,7 +1613,7 @@ def signature(obj): if sig is not None: # For classes and objects we skip the first parameter of their # __call__, __new__, or __init__ methods - return sig.replace(parameters=tuple(sig.parameters.values())[1:]) + return _signature_bound_method(sig) if isinstance(obj, types.BuiltinFunctionType): # Raise a nicer error message for builtins |