Python convert args to kwargs

If you're using Python >= 2.7 inspect.getcallargs() does this for you out of the box. You'd just pass it the decorated function as the first argument, and then the rest of the arguments exactly as you plan to call it. Example:

>>> def f(p1, p2, k1=None, k2=None, **kwargs):
...     pass
>>> from inspect import getcallargs

I'm planning to do f('p1', 'p2', 'p3', k2='k2', extra='kx1') (note that k1 is being passed positionally as p3), so...

>>> call_args = getcallargs(f, 'p1', 'p2', 'p3', k2='k2', extra='kx1')
>>> call_args
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'kwargs': {'extra': 'kx1'}}

If you know the decorated function won't use **kwargs, then that key won't appear in the dict, and you're done (and I'm assuming there's no *args, since that would break the requirement that everything have a name). If you do have **kwargs, as I have in this example, and want to include them with the rest of the named arguments, it takes one more line:

>>> call_args.update(call_args.pop('kwargs'))
>>> call_args
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'extra': 'kx1'}

Update: for Python >= 3.3, see inspect.Signature.bind() and the related inspect.signature function for functionality similar to (but more robust than) inspect.getcallargs().


Any arg that was passed positionally will be passed to *args. And any arg passed as a keyword will be passed to **kwargs. If you have positional args values and names then you can do:

kwargs.update(dict(zip(myfunc.func_code.co_varnames, args)))

to convert them all into keyword args.


Nadia's answer is correct, but I feel like a working demo of that answer is useful.

def decorator(func):
    def wrapped_func(*args, **kwargs):
        kwargs.update(zip(func.__code__.co_varnames, args))
        print(kwargs)
        return func(**kwargs)
    return wrapped_func

@decorator
def thing(a,b):
    return a+b

Given this decorated function, the following calls return the appropriate answer:

thing(1, 2)  # prints {'a': 1, 'b': 2}  returns 3
thing(1, b=2)  # prints {'b': 2, 'a': 1}  returns 3
thing(a=1, b=2)  # prints {'a': 1, 'b': 2}  returns 3

Note however that things start getting weird if you start nesting decorators because the decorated function now no longer takes a and b, it takes args and kwargs:

@decorator
@decorator
def thing(a,b):
    return a+b

Here thing(1,2) will print {'args': 1, 'kwargs': 2} and error with TypeError: thing() got an unexpected keyword argument 'args'


Note - co_varnames will include local variables as well as keywords. This probably won't matter, as zip truncates the shorter sequence, but may result in confusing error messages if you pass the wrong number of args.

You can avoid this with func_code.co_varnames[:func_code.co_argcount], but better is to use the inspect module. ie:

import inspect
argnames, varargs, kwargs, defaults = inspect.getargspec(func)

You may also want to handle the case where the function defines **kwargs or *args (even if just to raise an exception when used with the decorator). If these are set, the second and third result from getargspec will return their variable name, otherwise they will be None.