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)