Built with Tutify.io

    AIStudyLabs

    Writing Reusable Decorators (Logging, Timing, Caching)

    Writing Reusable Decorators (Logging, Timing, Caching)

    Introduction

    In this lesson, you'll advance your understanding of Python decorators by learning how to write reusable decorators for logging, timing, and caching, thus further enhancing the functionality and performance of your Python functions. You'll gain hands-on experience implementing these decorators and learn how to compose them safely while preserving original function metadata.

    Implementing Basic Function Decorators

    Decorators are a powerful feature in Python that allows you to modify the behavior of a function or class method. They use the concept of functions that return other functions and can be applied using the @decorator syntax.

    Here's a simple example of a decorator that prints a message before and after a function execution:

    import functools
    
    def my_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print("Before the function call")
            result = func(*args, **kwargs)
            print("After the function call")
            return result
        return wrapper
    
    @my_decorator
    def greet(name):
        print(f"Hello, {name}!")
    
    greet("Alice")
    
    # Output
    Before the function call
    Hello, Alice!
    After the function call
    

    Writing a Reusable Logging Decorator

    A logging decorator can be a useful tool for tracking function calls and their arguments, which is particularly handy during debugging.

    import functools
    
    # Logging decorator
    def log_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # Log function call
            print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
            result = func(*args, **kwargs)
            print(f"{func.__name__} returned {result}")
            return result
        return wrapper
    
    @log_decorator
    def add(x, y):
        return x + y
    
    add(5, 7)
    
    # Output
    Calling add with args: (5, 7), kwargs: {}
    add returned 12
    

    Creating a Timing Decorator

    To measure how long a function runs, a timing decorator can be implemented using Python's time module.

    import time
    import functools
    
    # Timing decorator
    def timer_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.perf_counter()
            result = func(*args, **kwargs)
            end_time = time.perf_counter()
            # Log execution time
            print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
            return result
        return wrapper
    
    @timer_decorator
    def calculate_sum(n):
        total = sum(range(n))
        return total
    
    calculate_sum(100000)
    
    # Output
    calculate_sum took 0.0054 seconds
    

    Building a Caching Decorator

    Caching can enhance performance by avoiding redundant function executions. This can be achieved by storing the results of expensive function calls.

    import functools
    
    # Caching decorator
    def cache_decorator(func):
        cache = {}
    
        @functools.wraps(func)
        def wrapper(*args):
            if args in cache:
                return cache[args]
            result = func(*args)
            cache[args] = result
            return result
        return wrapper
    
    @cache_decorator
    def fib(n):
        if n < 2:
            return n
        return fib(n - 1) + fib(n - 2)
    
    fib(10)
    
    # Output
    Manages internal cache to prevent recomputation
    

    Composing Multiple Decorators Safely

    Composing multiple decorators requires awareness of how they alter function metadata and stack composition. Utilize functools.wraps to preserve function metadata, such as its name and docstring.

    @timer_decorator
    @log_decorator
    def multiply(x, y):
        return x * y
    
    multiply(3, 4)
    
    # Output
    Calling multiply with args: (3, 4), kwargs: {}
    multiply returned 12
    multiply took 0.0000 seconds
    

    Summary / Key Takeaways

    In this lesson on writing reusable decorators, you have learned:

    Python Programming – Complete Course with Practical Examples — Advanced Python: Iterators, Generators, and Decorators

    Getting Started with Python and the Course

    Python Syntax, Indentation, and Core Concepts

    Variables, Data Types, and Type Conversion

    Operators, Expressions, and Control Flow

    Loops and Iteration Patterns

    Working with Strings in Depth

    Collections: Lists, Tuples, Sets, and Dictionaries

    Functions and Program Structuring

    Functional Programming Concepts in Python

    Modules, Packages, and Code Organization

    Error Handling and Debugging Fundamentals

    File Handling and Data Formats

    Object-Oriented Programming in Python

    Advanced Python: Iterators, Generators, and Decorators

    Python Standard Library for Real-World Tasks

    Python for Automation and Scripting

    Testing and Quality Assurance in Python

    Tutorial Not Found