Skip to pass arguments to decorators if you already know what a decorator is

What is a decorator?

A decorator allows the user to add functionality to an existing function without modifying its structure.

For example, We might need a way to authenticate an API call, and only then allow some data to be returned. We can streamline the authentication process by abstracting it into a decorator function.

Consider the following function that needs to be authenticated,

def foo(user_id):
   # perform some DB calls
   # perform some DB writes
   pass

We can use a decorator as follows,

def authenticate(func):
   def wrapper(*args, **kwargs):
       # perform auth checks
       if auth:
          return func(*args, **kwargs)
       else:
          raise Exception('not authenticated')

   return wrapper

And hence, authenticating foo is as simple as,

@authenticate
def foo(user_id):
   # perform some DB calls
   # perform some DB writes
   pass

Pass arguments to decorators

Now that we have some knowledge of decorators, let us proceed to something more interesting. How would we pass arguments to decorators?

This is where it gets a little confusing - we will need to create a decorator factory that returns a particular decorator based on the arguments passed.

A simple use case could be when we want to call a function multiple times, but we do not want to write it in an explicit for loop, and neither do we want to use a for loop inside the function itself.

For example, we would want to call the function foo below, 10 times:

def foo():
   print("Hello World")

In this scenario, we can create a decorator factory that takes in a number and returns a decorator that calls a function a number of times.

def call(number):  # Decorator factory
    def decorator(func):
        def wraps(*args, **kwargs):
            for i in range(number):
                func(*args, **kwargs)

        return wraps

    return decorator

And to use the decorator factory above,

@call(10)
def foo():
   print("Hello World")

When we call foo now,

>>> foo()
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World

Thanks for reading! Please hit me up at sanjay.rohit2@gmail.com for any feedback :)