Using a class-based decorator is the way to go if you want your decorator to accept arguments. In my experience they end up being easier to reason about and maintain. They are quite simple, __init__ accepts any arguments for the decorator, __call__ returns the decorated function. One of the gotchas with writing decorators is that they behave completely different if they are passed arguments or not. It's pretty easy to account for this in a class-based decorator, allowing your decorator to accept arguments or not:
class target:
def __init__(self, targets):
"""Arguments for decorator"""
self.targets = None
if not hasattr(targets, '__call__'):
# check we are actually passed arguments!
# if targets has __call__ attr, we were called w/o arguments
self.targets = targets
def __call__(self, f):
"""Returns decorated function"""
if self.targets:
def newf(*args, **kwargs):
for target in self.targets:
f(target)
return newf
else:
return f
Now if we use the decorator with arguments, it will work as expected, calling our function 3 times:
>>> @target([1,2,3])
..: def foo(x): print x
...
>>> foo()
1
2
3
However, if we are not called with arguments, we'll return the original function instead:
>>> @target
def foo(x): print x
..:
>>> foo(3)
<<< 3