I am writing a function decorator that will apply a conversion to the first argument of the function. It works fine if I only decorate my functions once but if I decorate them twice I get an error. Below is some code that demonstrates the problem, it is a simplified version of the code I'm working on. I have excluded the code that does the conversion so as to not distract from the problem
from inspect import getargspec
from functools import wraps
def dec(id):
def _dec(fn):
@wraps(fn)
def __dec(*args, **kwargs):
if len(args):
return fn(args[0], *args[1:], **kwargs)
else:
first_arg = getargspec(fn).args[0]
new_kwargs = kwargs.copy()
del new_kwargs[first_arg]
return fn(kwargs[first_arg], **new_kwargs)
return __dec
return _dec
@dec(1)
def functionWithOneDecorator(a, b, c):
print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c)
@dec(1)
@dec(2)
def functionWithTwoDecorators(a, b, c):
print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c)
functionWithOneDecorator(1, 2, 3)
functionWithOneDecorator(1, b=2, c=3)
functionWithOneDecorator(a=1, b=2, c=3)
functionWithOneDecorator(c=3, b=2, a=1)
functionWithTwoDecorators(1, 2, 3)
functionWithTwoDecorators(1, b=2, c=3)
functionWithTwoDecorators(a=1, b=2, c=3)
functionWithTwoDecorators(c=3, b=2, a=1)
When I run the above code I get the following output:
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithTwoDecorators(a = 1, b = 2, c = 3)
functionWithTwoDecorators(a = 1, b = 2, c = 3)
IndexError: list index out of range
This is because when the second decorator inspects the function it is decorating to find the argument names and fails because it is decorating a decorator and that only takes *args and **kwargs.
I can think of ways around the problem which would work in the code above but would still break if a function was decorated with my decorator and another from a 3rd party. Is there a general way to fix this? or is there a better way to achieve the same result?
Update: Thanks to @Hernan for pointing out the decorator module. It solves this problem exactly. Now my code looks like this:
from decorator import decorator
def dec(id):
@decorator
def _dec(fn, *args, **kwargs):
return fn(args[0], *args[1:], **kwargs)
return _dec
@dec(1)
def functionWithOneDecorator(a, b, c):
print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c)
@dec(1)
@dec(2)
def functionWithTwoDecorators(a, b, c):
print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c)
functionWithOneDecorator(1, 2, 3)
functionWithOneDecorator(1, b=2, c=3)
functionWithOneDecorator(a=1, b=2, c=3)
functionWithOneDecorator(c=3, b=2, a=1)
functionWithTwoDecorators(1, 2, 3)
functionWithTwoDecorators(1, b=2, c=3)
functionWithTwoDecorators(a=1, b=2, c=3)
functionWithTwoDecorators(c=3, b=2, a=1)
Much cleaner, and it works!