Skip to content

Decorators

Understanding Decorators with a Detailed Example

Scenario:

Let's say we want to log whenever a function is called. Instead of adding logging code inside every function, we can use a decorator.


Step 1: Creating a Decorator Function

A decorator is a function that takes another function as an argument, wraps it in additional functionality, and returns the modified function.

import time

def logger_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"[LOG] Function '{func.__name__}' is about to be called with arguments: {args}, {kwargs}")

        start_time = time.time()  # Start timing the function execution
        result = func(*args, **kwargs)  # Call the original function
        end_time = time.time()  # End timing

        print(f"[LOG] Function '{func.__name__}' executed in {end_time - start_time:.4f} seconds")
        print(f"[LOG] Function '{func.__name__}' returned: {result}\n")

        return result  # Return the original function's result
    return wrapper

Step 2: Applying the Decorator

Now, we can use @logger_decorator to log function calls.

@logger_decorator
def add(a, b):
    time.sleep(1)  # Simulating a delay
    return a + b

@logger_decorator
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

# Calling the functions
add(5, 3)
greet("Alice")
greet("Bob", greeting="Hi")

Step 3: Understanding the Output

When we run the above code, we get:

[LOG] Function 'add' is about to be called with arguments: (5, 3), {}
[LOG] Function 'add' executed in 1.0003 seconds
[LOG] Function 'add' returned: 8

[LOG] Function 'greet' is about to be called with arguments: ('Alice',), {}
[LOG] Function 'greet' executed in 0.0000 seconds
[LOG] Function 'greet' returned: Hello, Alice!

[LOG] Function 'greet' is about to be called with arguments: ('Bob',), {'greeting': 'Hi'}
[LOG] Function 'greet' executed in 0.0000 seconds
[LOG] Function 'greet' returned: Hi, Bob!

Step 4: Explanation

1. Decorator (logger_decorator)

  • Takes a function func as input.
  • Defines an inner wrapper function that:
    • Logs the function call.
    • Measures execution time.
    • Calls the original function.
    • Logs the return value.
    • Returns the function’s result.
  • Returns the wrapper function.

2. Applying the Decorator

  • The @logger_decorator syntax applies the decorator to add() and greet().
  • Instead of calling add() directly, Python calls wrapper(), which logs details and executes add().

3. Flexible Arguments (*args, **kwargs)

  • Allows the decorator to work with any function, no matter its parameters.

Why Use Decorators?

Code Reusability – You don’t need to modify every function manually.
Separation of Concerns – The function logic remains clean, and logging/debugging is handled separately.
Scalability – You can add more decorators (e.g., caching, authentication) without modifying the core function.