Sometimes inside a decorator that creates a function with a generic (args, *kwargs) signature, you want to access a value passed for a particular parameter name to a wrapped function, but don't know whether that value will be passed as a positional or keyword argument, or whether the wrapped function defines a default value for the parameter. The following utility function extracts this information for you.
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 | import inspect
def get_arg_value(func, argname, args, kwargs):
"""
This function is meant to be used inside decorators, when you want
to find what value will be available inside a wrapped function for
a particular argument name. It handles positional and keyword
arguments and takes into account default values. For example:
>>> def foo(x, y): pass
...
>>> get_arg_value(foo, 'y', [1, 2], {})
2
>>> get_arg_value(foo, 'y', [1], {'y' : 2})
2
>>> def foo(x, y, z=300): pass
...
>>> get_arg_value(foo, 'z', [1], {'y' : 2})
300
>>> get_arg_value(foo, 'z', [1], {'y' : 2, 'z' : 5})
5
"""
# first check kwargs
if argname in kwargs:
return kwargs[argname]
# OK. could it be a positional argument?
regargs, varargs, varkwargs, defaults=inspect.getargspec(func)
if argname in regargs:
regdict=dict(zip(regargs, args))
if argname in regdict:
return regdict[argname]
defaultdict=dict(zip(reversed(regargs), defaults))
if argname in defaultdict:
return defaultdict[argname]
raise ValueError("no such argument: %s" % argname)
|
As an example, you might want to write a decorator that checks permissions for some underlying operations, and have a naming convention for function parameters for the methods that implement these operations, so that the parameter "item_id" is always used for items that have permission levels associated with them. However, the signatures of these methods are all different; some may not use the item_id parameter at all. The decorator, then, when it invokes the wrapped method needs to do so in a generic fashion, without specifically passing in item_id. Therefore, some sort of introspection is necessary to obtain the value, if it exists. That's what this gives you; you'd call "item_id = get_arg_value(wrapped_func, 'item_id', args, kwargs)" inside the decorator, taking care to catch any ValueErrors that might result. A variant of this function that accepts a default value might also be useful.
mistake in docstring. Your first example should return 2, not 1.
Yes, indeed! I've fixed it. Thanks.