Suppose I have the following decorator. (To repeat a function n times)
def repeat(num_times=4):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
Now, it does have a default value of 4, however, even if I want to call it with default value, I still have to call it as follows
@repeat()
def my_function():
print("hello")
instead of
@repeat
def my_function():
print("hello")
Now, I can change the definition of my decorator to
def repeat(_func=None, *, num_times=2):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
if _func is None:
return decorator_repeat
else:
return decorator_repeat(_func)
And enable the functionality to call it without arguments if I want to.
However, can this be achieved without changing the code of the decorator, but by defining another decorator?
i.e. I want to define a decorator enable_direct
so that I can just add @enable_direct
to my decorator definition and have the same effect. (i.e. as follows)
@enable_direct
def repeat(num_times=4):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
Note:
I am aware of the solution mentioned in How to create a Python decorator that can be used either with or without parameters?
The definitions in that question have a different signature, and if one is starting afresh, one can follow that pattern. However, say I have 20-30 such decorator definitions (3 level nested). I want all of these to be enabled to be called without parentheses.
The def repeat
statement does not have a function argument. The functions in that question have a 2 level nesting, while mine has 3 level. I wanted to ask if it is possible with such decorator definitions (which are meant to be called with parentheses) without changing the function definition. The accepted answer there has a different signature, and thus does not meat requirement in this question.
Note 2: I did not ask this question before trying out the definition of double wrap given there. Calling it without parentheses returns another function (if the signature of function is as described).
Here you are:
import functools
def enable_direct(decorator):
@functools.wraps(decorator)
def wrapper(*args, **kwargs):
f = args[0]
if callable(f):
return decorator()(f) # pass the function to be decorated
else:
return decorator(*args, **kwargs) # pass the specified params
return wrapper
@enable_direct
def repeat(num_times=4):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
@repeat
def my_func(name):
print(name)
@repeat(2)
def my_func2(name):
print(name)
print(my_func)
print(my_func2)
my_func("Gino")
my_func2("Mario")
Which produces
<function my_func at 0x7f629f091b70>
<function my_func2 at 0x7f629f091bf8>
Gino
Gino
Gino
Gino
Mario
Mario