Python decorator

With the help of decorator, you can change the behavior of a function without modifying the function code. This means, you could execute some code that you would like to before/after the original function and anyone, who is calling this function, would not need to change even a single line of code.

Some of the simplest examples of this additional code are

  1. logging before calling the original function.

  2. measuring the time a function takes to run.

  3. caching the output of a function.

Python decorator is a function which takes another function as input and returns a function.

def my_decorator(func):
  def new_function(*args,**kwargs):
    # do some work    
  return new_function

This is the basic concept you need to understand. We will get into details about how decorator works and how to write it.

You can apply a decorator to a function using @ sybmol

def my_decorator(func):
  def new_function(*args,**kwargs):
    # do some work
  return new_function

@my_decorator
def foo(args):
  pass

Now lets get into some details.

Initially we had a function called foo. Note that foo is just a name which is pointing to a function object.

Screenshot from 2022-09-18 12-48-47.png

When you apply a decorator, it does the following

foo = my_decorator(foo)

Since my_decorator is a function which takes a function and returns a function, it took foo function as input and returned a new function and most importantly assigned the new function to foo. So now foo is pointing to new _function instead of original function.

Screenshot from 2022-09-18 12-53-46.png

decorator takes the function as input on which it is applied.

After applying the decorator, there is no change required in the calling code. That means this new function should be able to accept the same arguments which the original function was accepting. To do this, decorator makes use of args and kwargs.

def my_decorator(func):
  def new_function(*args,**kwargs):
    # do some work
    print('calling foo')
    result = func(*args, **kwargs)
    return result
  return new_function

@my_decorator
def foo(name,age):
  pass

foo('abc',20)

# this is translated to 
new_function = my_decorator(foo)
foo = new_function
foo('abc',20)

#or 
my_decorator(foo)('abc',20)

In this example, decorator created a function which logs a statement before calling the original function. Then calls the original function with the arguments passed and returns the result.

This is the simplest form of decorator. There are some advance usage of decorator like property which we will get into later.