This recipe implements in pure Python the algorithm used by the interpreter for binding the values passed as parameters in a function call to the formal arguments of the function.
This is useful for decorators that want to take into account this binding when wrapping a function (e.g. for type checking).
Edit (2008/5/10): Added a second returned value, missing_args: a tuple of the formal parameters whose value was not provided (i.e. those using the respective default value). This is useful in cases where one want to distinguish f() from f(None) given "def f(x=None):".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | def getcallargs(func, *args, **kwds):
'''Get the actual value bounded to each formal parameter when calling
`func(*args,**kwds)`.
It works for methods too (bounded, unbounded, staticmethods, classmethods).
@returns: `(bindings, missing_args)`, where:
- `bindings` is a mapping of every formal parameter (including *varargs
and **kwargs if present) of the function to the respective bounded value.
- `missing_args` is a tuple of the formal parameters whose value was not
provided (i.e. using the respective default value)
Examples::
>>> def func(a, b='foo', c=None, *x, **y):
... pass
>>> getcallargs(func, 5)
({'a': 5, 'y': {}, 'c': None, 'b': 'foo', 'x': ()}, ('b', 'c'))
>>> getcallargs(func, 5, 'foo')
({'a': 5, 'y': {}, 'c': None, 'b': 'foo', 'x': ()}, ('c',))
>>> getcallargs(func, 5, c=['a', 'b'])
({'a': 5, 'y': {}, 'c': ['a', 'b'], 'b': 'foo', 'x': ()}, ('b',))
>>> getcallargs(func, 5, 6, 7, 8)
({'a': 5, 'y': {}, 'c': 7, 'b': 6, 'x': (8,)}, ())
>>> getcallargs(func, 5, z=3, b=2)
({'a': 5, 'y': {'z': 3}, 'c': None, 'b': 2, 'x': ()}, ('c',))
'''
arg2value = {}
f_name = func.func_name
spec_args, varargs, varkw, defaults = getargspec(func)
# handle methods
if ismethod(func):
# implicit 'self' (or 'cls' for classmethods) argument: func.im_self
if func.im_self is not None:
arg2value[spec_args.pop(0)] = func.im_self
elif not args or not isinstance(args[0], func.im_class):
got = args and ('%s instance' % type(args[0]).__name__) or '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_args = len(args)
has_kwds = bool(kwds)
num_spec_args = len(spec_args)
num_defaults = len(defaults or ())
# get the expected arguments passed positionally
arg2value.update(izip(spec_args,args))
# get the expected arguments passed by name
for arg in spec_args:
if arg in kwds:
if arg in arg2value:
raise TypeError("%s() got multiple values for keyword "
"argument '%s'" % (f_name,arg))
else:
arg2value[arg] = kwds.pop(arg)
# fill in any missing values with the defaults
missing = []
if defaults:
for arg,val in izip(spec_args[-num_defaults:],defaults):
if arg not in arg2value:
arg2value[arg] = val
missing.append(arg)
# ensure that all required args have a value
for arg in spec_args:
if arg not in arg2value:
num_required = num_spec_args - num_defaults
raise TypeError('%s() takes at least %d %s argument%s (%d given)'
% (f_name, num_required,
has_kwds and 'non-keyword ' or '',
num_required>1 and 's' or '', num_args))
# handle any remaining named arguments
if varkw:
arg2value[varkw] = kwds
elif kwds:
raise TypeError("%s() got an unexpected keyword argument '%s'" %
(f_name, iter(kwds).next()))
# handle any remaining positional arguments
if varargs:
if num_args > num_spec_args:
arg2value[varargs] = args[-(num_args-num_spec_args):]
else:
arg2value[varargs] = ()
elif num_spec_args < num_args:
raise TypeError('%s() takes %s %d argument%s (%d given)' %
(f_name, defaults and 'at most' or 'exactly',
num_spec_args, num_spec_args>1 and 's' or '', num_args))
return arg2value, tuple(missing)
if __name__ == '__main__':
import doctest
doctest.testmod()
|
There are a couple of things you forgot to import