Welcome, guest | Sign In | My Account | Store | Cart
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()

History

  • revision 2 (14 years ago)
  • previous revisions are not available