top of page
  • Writer's pictureSunil Kumar Yadav

Python Decorators Simplified

Updated: Jul 21


Decorators are very powerful tool for Python programmer. It allows Python programmers to modify the behavior of a function or a class. Decorators allow us to wrap another function in order to extend the behavior of the wrapped function, without permanently modifying it.


Decorators are like the fairy godmothers of functions - they sprinkle their magic dust and transform ordinary functions into something extraordinary.

But before you start playing with decorators, let's make sure you've got your wizard hat and wand ready to grasp some fundamental concepts.



Understanding First Class Objects!


In Python, all entities, including functions, are considered first-class objects. This means they can be stored in variables, passed as arguments to other functions, returned as values from other functions, and included in data structures. The concept of first-class objects is fundamental to understanding Python's flexible and dynamic nature.


A first-class object is an entity that supports all the operations generally available to other entities. This includes:

  • Assignment to Variables: You can assign functions, classes, and other objects to variables.

  • Passing as Arguments: Functions and other objects can be passed as arguments to other functions.

  • Returning from Functions: Functions can return other functions or objects.

  • Storing in Data Structures: Functions and other objects can be stored in lists, dictionaries, and other data structures.


Here is a simple example to help understand this concept. The code snippet shows how to pass a function as an argument to another function.


# Higher order function, passing function as argument
def greet(func):
    func()

# Nested function. Calling new_greet returns func i.e 5
def new_greet():
    def func():
        return 5
    return func

def print_msg():
    print("sample message!")

print(greet(print_msg)) 

Output

sample message!
None                  # Printing greet() result in None


Understanding Decorator With Simple Example


Now lets use this information and create our own simple decorator.

def my_decorator(func):
    """
    decorator function will call the function on which its 
    being applied and additional routines mentioned in 
    wrapper function will execute.
    """
    def wrapper_function():
        print("----- Start of decorator ---------")
        func()
        print("----- End of decorator ---------")
    return wrapper_function


@my_decorator
def greeting():
    print("Hello")

greeting()

Output

----- Start of decorator ---------
Hello
----- End of decorator ---------

As shown in above example, @my_decorator can be added to any function whose declaration marches to wrapper_function() and this will cause printing decorator start and end string before and after that function call.


As you can observe this decorator only works for functions those do not have any input argument i.e. if greeting() function changed to greeting(msg) then decorator will not work. To rectify this limitation, we will use arg and keyword arg in func().


def my_new_decorator(func):
     def wrapper_function(*args, **kwargs):
        print("----- Start of decorator ---------")
        func()
        print("----- End of decorator ---------")
    return wrapper_function

@my_new_decorator
def custom_greeting(name):
    print("Hello ", name)

@my_new_decorator
def greeting2():
    print("Hello 2")

custom_greeting('Peter')
greeting2()

Output

----- Start of decorator ---------
Hello Peter
----- End of decorator ---------
----- Start of decorator --------- 
Hello 2
----- End of decorator ---------


Where Decorators Can Be Used?

Now we know what decorators are, below are few simple example where one can use decorator.


1. Timing Decorator

A timing decorator can be used to log the execution of a function. It is useful during debugging performance related issue and this decorator can be sprinkled around various function to identify bottlenecks.


import time

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} took {end_time - start_time} seconds to execute")
        return result
    return wrapper

@timing_decorator
def get_all_posts():
    # Logic to retrieve all blog posts
    pass

2. Authentication Decorator

Ensures that a user is authenticated before they can perform certain actions, such as creating or editing a blog post.


def requires_authentication(func):
    def wrapper(*args, **kwargs):
        user = get_current_user()
        if not user.is_authenticated:
            raise PermissionError("You must be logged in to perform this action.")
        return func(*args, **kwargs)
    return wrapper

@requires_authentication
def create_post(title, content):
    # Logic to create a blog post
    pass

3. Logging Decorator

 Logs information about function calls, which can be useful for debugging and monitoring.


import logging

def log_function_call(func):
    def wrapper(*args, **kwargs):
        logging.info(f"Calling function {func.__name__} with args {args} and kwargs {kwargs}")
        result = func(*args, **kwargs)
        logging.info(f"Function {func.__name__} returned {result}")
        return result
    return wrapper

@log_function_call
def edit_post(post_id, title, content):
    # Logic to edit a blog post
    pass


33 views0 comments

Comments


bottom of page