Blog

Useful Decorators.

Decorators play a vital role in augmenting the functionality of functions and methods without altering their core logic. Diving into three specific decorators designed to track different aspects of function execution, count, time, and caller details. These decorators are useful tools for debugging, optimization, and monitoring in software development.

1. Counting Function Executions with counter

The counter decorator is designed to keep track of how many times a particular function is executed. This is particularly useful for understanding the usage pattern of a function within an application, aiding in both debugging and performance optimization.

from functools import wraps

def counter(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        wrapper.count += 1
        print(f"\n> Counting execution of '{func.__name__}': {wrapper.count}.")
        return func(*args, **kwargs)

    wrapper.count = 0
    return wrapper

2. Measuring Execution Time with stopwatch

The stopwatch decorator measures the execution time of the decorated function. This information is critical for identifying performance bottlenecks and optimizing slow-running functions to improve the overall efficiency of an application.

from functools import wraps
from timeit import default_timer as timer

def stopwatch(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = timer()
        result = func(*args, **kwargs)
        print(f"\n> Execution of '{func.__name__}' in: {(timer() - start):.4f} sec.")
        return result

    return wrapper

3. Inspecting Caller Details with inspect_caller

The inspect_caller decorator provides insights into the caller's context of a function. It prints the file name, function name, and line number from where the decorated function was called. This decorator is invaluable for tracing the flow of execution and debugging complex applications with multiple layers of function calls.

import inspect
from functools import wraps

def inspect_caller(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        current_frame = inspect.currentframe()
        prev_caller_frame = inspect.getouterframes(current_frame, 2)[1]

        filename = prev_caller_frame.filename
        line_num = prev_caller_frame.lineno
        func_name = prev_caller_frame.function

        print(f"\n> Function '{func.__name__}' was called from file '{filename}', from function '{func_name}', at line '{line_num}'")
        return func(*args, **kwargs)

    return wrapper