I\'d like to create a Python decorator that can be used either with parameters:
@redirect_output(\"somewhere.log\")
def foo():
....
or
Since no one mentioned this, there is also a solution utilizing callable class which I find more elegant, especially in cases where the decorator is complex and one may wish to split it to multiple methods(functions). This solution utilizes __new__ magic method to do essentially what others have pointed out. First detect how the decorator was used than adjust return appropriately.
class decorator_with_arguments(object):
def __new__(cls, decorated_function=None, **kwargs):
self = super().__new__(cls)
self._init(**kwargs)
if not decorated_function:
return self
else:
return self.__call__(decorated_function)
def _init(self, arg1="default", arg2="default", arg3="default"):
self.arg1 = arg1
self.arg2 = arg2
self.arg3 = arg3
def __call__(self, decorated_function):
def wrapped_f(*args):
print("Decorator arguments:", self.arg1, self.arg2, self.arg3)
print("decorated_function arguments:", *args)
decorated_function(*args)
return wrapped_f
@decorator_with_arguments(arg1=5)
def sayHello(a1, a2, a3, a4):
print('sayHello arguments:', a1, a2, a3, a4)
@decorator_with_arguments()
def sayHello(a1, a2, a3, a4):
print('sayHello arguments:', a1, a2, a3, a4)
@decorator_with_arguments
def sayHello(a1, a2, a3, a4):
print('sayHello arguments:', a1, a2, a3, a4)
If the decorator is used with arguments, than this equals:
result = decorator_with_arguments(arg1=5)(sayHello)(a1, a2, a3, a4)
One can see that the arguments arg1 are correctly passed to the constructor and the decorated function is passed to __call__
But if the decorator is used without arguments, than this equals:
result = decorator_with_arguments(sayHello)(a1, a2, a3, a4)
You see that in this case the decorated function is passed directly to the constructor and call to __call__ is entirely omitted. That is why we need to employ logic to take care of this case in __new__ magic method.
Why can't we use __init__ instead of __new__? The reason is simple: python prohibits returning any other values than None from __init__
WARNING
This approcach has one side effect. It will not preserve function signature!