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).
| Python |
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 | from itertools import izip
from inspect import getargspec, ismethod
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).
@rtype: dict
@returns: A mapping of every formal parameter (including *varargs
and **kwargs if present) of the function to the respective bounded value.
Examples::
>>> def func(a, b='foo', c=None, *x, **y):
... pass
>>> sorted(getcallargs(func, 5).items())
[('a', 5), ('b', 'foo'), ('c', None), ('x', ()), ('y', {})]
>>> sorted(getcallargs(func, 5, 'bar').items())
[('a', 5), ('b', 'bar'), ('c', None), ('x', ()), ('y', {})]
>>> sorted(getcallargs(func, 5, c=['a', 'b']).items())
[('a', 5), ('b', 'foo'), ('c', ['a', 'b']), ('x', ()), ('y', {})]
>>> sorted(getcallargs(func, 5, 6, 7, 8).items())
[('a', 5), ('b', 6), ('c', 7), ('x', (8,)), ('y', {})]
>>> sorted(getcallargs(func, 5, z=3, b=2).items())
[('a', 5), ('b', 2), ('c', None), ('x', ()), ('y', {'z': 3})]
'''
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
if defaults:
for arg,val in izip(spec_args[-num_defaults:],defaults):
if arg not in arg2value:
arg2value[arg] = val
# 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
|


Sign in to comment