diff -r 744d673392b4 Lib/inspect.py --- a/Lib/inspect.py Sat Jul 26 17:43:01 2014 +0200 +++ b/Lib/inspect.py Fri Aug 01 09:18:12 2014 +0200 @@ -17,7 +17,7 @@ getmodule() - determine the module that an object came from getclasstree() - arrange classes so as to represent their hierarchy - getargspec(), getargvalues() - get info about function arguments + getargspec(), getargvalues(), getcallargs() - get info about function arguments formatargspec(), formatargvalues() - format an argument spec getouterframes(), getinnerframes() - get info about frames currentframe() - get the current stack frame @@ -826,6 +826,92 @@ args, varargs, varkw = getargs(frame.f_code) return ArgInfo(args, varargs, varkw, frame.f_locals) +def getcallargs(func, *positional, **named): + """Get the mapping of arguments to values when calling func(*positional, **named). + + A dict is returned, with keys the function argument names (including the + names of the * and ** arguments, if any), and values the respective bound + values from 'positional' and 'named'.""" + args, varargs, varkw, defaults = getargspec(func) + f_name = func.__name__ + arg2value = {} + + # the following closures are basically because of tuple parameter unpacking + assigned_tuple_params = [] + def assign(arg, value): + if isinstance(arg, str): + arg2value[arg] = value + else: + assigned_tuple_params.append(arg) + value = iter(value) + for i, subarg in enumerate(arg): + try: + subvalue = next(value) + except StopIteration: + raise ValueError('need more than %d %s to unpack' % + (i, 'values' if i>1 else 'value')) + assign(subarg,subvalue) + try: next(value) + except StopIteration: pass + else: raise ValueError('too many values to unpack') + def is_assigned(arg): + if isinstance(arg,str): + return arg in arg2value + return arg in assigned_tuple_params + if ismethod(func): + # implicit 'self' (or 'cls' for classmethods) argument + if func.im_self is not None: + positional = (func.im_self,) + positional + elif not positional or not isinstance(positional[0], func.im_class): + got = ('%s instance' % type(positional[0]).__name__ if positional + else 'nothing') + raise TypeError('unbound method %s() must be called with %s ' + 'instance as first argument (got %s instead)' % + (f_name, func.im_class.__name__, got)) + num_pos = len(positional) + num_total = num_pos + len(named) + num_args = len(args) + num_defaults = len(defaults) if defaults else 0 + for arg, value in zip(args, positional): + assign(arg, value) + if varargs: + if num_pos > num_args: + assign(varargs, positional[-(num_pos-num_args):]) + else: + assign(varargs, ()) + elif 0 < num_args < num_pos: + raise TypeError('%s() takes %s %d %s (%d given)' % ( + f_name, 'at most' if defaults else 'exactly', num_args, + 'arguments' if num_args>1 else 'argument', num_total)) + elif num_args == 0 and num_total: + raise TypeError('%s() takes no arguments (%d given)' % (f_name, num_total)) + for arg in args: + if isinstance(arg, str) and arg in named: + if is_assigned(arg): + raise TypeError("%s() got multiple values for keyword " + "argument '%s'" % (f_name, arg)) + else: + assign(arg, named.pop(arg)) + if defaults: # fill in any missing values with the defaults + for arg, value in zip(args[-num_defaults:], defaults): + if not is_assigned(arg): + assign(arg, value) + if varkw: + assign(varkw, named) + elif named: + unexpected = next(iter(named)) + if isinstance(unexpected, unicode): + unexpected = unexpected.encode(sys.getdefaultencoding(), 'replace') + raise TypeError("%s() got an unexpected keyword argument '%s'" % + (f_name, unexpected)) + unassigned = num_args - len([arg for arg in args if is_assigned(arg)]) + if unassigned: + num_required = num_args - num_defaults + raise TypeError('%s() takes %s %d %s (%d given)' % ( + f_name, 'at least' if defaults else 'exactly', num_required, + 'arguments' if num_required>1 else 'argument', num_total)) + return arg2value + def joinseq(seq): if len(seq) == 1: return '(' + seq[0] + ',)'