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 f
Here's a newer method to solve this using inspect.signature
(for Python 3.3+). I'll give an example that can be run / tested yourself first and then show how to modify the original code with it.
Here's a test function which just sums up any args/kwargs given to it; at least one argument is required (a
) and there's one keyword-only argument with a default value (b
), just to test different aspects of function signatures.
def silly_sum(a, *args, b=1, **kwargs):
return a + b + sum(args) + sum(kwargs.values())
Now let's make a wrapper for silly_sum
which can be called in the same way as silly_sum
(with an exception which we'll get to) but that only passes in kwargs to the wrapped silly_sum
.
def wrapper(f):
sig = inspect.signature(f)
def wrapped(*args, **kwargs):
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
print(bound_args) # just for testing
all_kwargs = bound_args.arguments
assert len(all_kwargs.pop("args")) == 0
all_kwargs.update(all_kwargs.pop("kwargs"))
return f(**all_kwargs)
return wrapped
sig.bind
returns a BoundArguments
object, but this doesn't take defaults into account unless you call apply_defaults
explicitly. Doing so will also generate an empty tuple for args and an empty dict for kwargs if no *args
/**kwargs
were given.
sum_wrapped = wrapper(silly_sum)
sum_wrapped(1, c=9, d=11)
# prints
# returns 22
Then we just get the dictionary of arguments and add any **kwargs
in. The exception to using this wrapper is that *args
can't be passed to the function. This is because there are no names for these, so we can't convert them into kwargs. If passing them through as a kwarg named args is acceptable, that could be done instead.
Here is how this can be applied to the original code:
import inspect
class mydec(object):
def __init__(self, f, *args, **kwargs):
self.f = f
self._f_sig = inspect.signature(f)
def __call__(self, *args, **kwargs):
bound_args = self._f_sig.bind(*args, **kwargs)
bound_args.apply_defaults()
all_kwargs = bound_args.arguments
assert len(all_kwargs.pop("args")) == 0
all_kwargs.update(all_kwargs.pop("kwargs"))
hozer(**all_kwargs)
self.f(*args, **kwargs)