I am writing a decorator that needs to call other functions prior to call of the function that it is decorating. The decorated function may have positional arguments, but the functions the decorator will call can only accept keyword arguments. Does anyone have a handy way of converting positional arguments into keyword arguments?
I know that I can get a list of the variable names of the decorated function:
>>> def a(one, two=2):
... pass
>>> a.func_code.co_varnames
('one', 'two')
But I can't figure out how to tell what was passed in positionally and what was as keyword.
My decorator looks like this:
class mydec(object):
def __init__(self, f, *args, **kwargs):
self.f = f
def __call__(self, *args, **kwargs):
hozer(**kwargs)
self.f(*args, **kwargs)
Is there a way other than just comparing kwargs and co_varnames, adding to kwargs anything not in there, and hoping for the best?
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.
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()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With