|
Description:
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).
Source: Text Source
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)
if ismethod(func):
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 ())
arg2value.update(izip(spec_args,args))
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)
if defaults:
for arg,val in izip(spec_args[-num_defaults:],defaults):
if arg not in arg2value:
arg2value[arg] = val
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))
if varkw:
arg2value[varkw] = kwds
elif kwds:
raise TypeError("%s() got an unexpected keyword argument '%s'" %
(f_name, iter(kwds).next()))
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
Discussion:
|