You can also create more complex Decorators that take Arguments. In order to achieve that, you need to create a decorator factory that will return a decorator. This basically means that you can add another layer of abstraction to your Decorators.

In order to see that in action, let’s create a decorator that will print the time it took for a function to execute. We can do it like this:

from functools import wraps
from time import time
 
def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time()
        result = func(*args, **kwargs)
        end = time()
        print(f'Time elapsed: {end - start}')
        return result
    return wrapper
 
@timer
def func():
    pass

Now, if we call func(), it will print the time it took for it to execute.

We can also create a decorator that will print the time it took for a function to execute only if the function took more than a certain amount of time to execute. We can do it like this:

from functools import wraps
from time import time
 
def timer(min_time=0):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time()
            result = func(*args, **kwargs)
            end = time()
            if end - start > min_time:
                print(f'Time elapsed: {end - start}')
            return result
        return wrapper
    return decorator
 
@timer(min_time=1)
def func():
    pass

Now, if we call func(), it will print the time it took for it to execute only if it took more than 1 second to execute.

This works because the timer function is a decorator factory that returns a decorator which is then used to decorate the func function.

I know that this is a bit confusing, but it is a very powerful feature of Python and it is worth learning it. If you want to learn more about Decorators, I recommend reading this article.