1

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).

4
  • 1
    It's a slightly different question, but the accepted answer does answer this question. Commented Oct 31, 2019 at 15:40
  • 1
    One of the answers does show a decorator being used as both @foo (not just @foo()) and @foo(...). Commented Oct 31, 2019 at 16:51
  • The definitions in that question have a different signature. In the current one, 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. Commented Oct 31, 2019 at 17:15
  • please see my revised answer Commented Oct 31, 2019 at 19:15

1 Answer 1

2

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 
Sign up to request clarification or add additional context in comments.

3 Comments

We didn't need @enable_direct for this. @repeat(4) works without that too. What needs to also work is @repeat without parentheses.
Please see my revised answer @Hrishikesh
Great. Thank you!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.