Decorators and Context Managers

Python provides powerful features like decorators and context managers to simplify and enhance your code. They help in creating reusable, concise, and efficient functionality.


Decorators

A decorator is a function that modifies the behavior of another function or method. They are used for logging, enforcing access control, instrumentation, and more.

Basic Syntax

def decorator(func):
    def wrapper():
        print("Before the function call")
        func()
        print("After the function call")
    return wrapper

@decorator
def say_hello():
    print("Hello!")

say_hello()

Explanation:

  1. The @decorator syntax applies the decorator function to say_hello.
  2. The wrapper function modifies the behavior of say_hello.

Common Use Cases of Decorators

1. Logging

def log(func):
    def wrapper(*args, **kwargs):
        print(f"Function {func.__name__} called with arguments {args} and {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log
def add(a, b):
    return a + b

print(add(3, 5))

2. Authentication

def requires_auth(func):
    def wrapper(user):
        if not user.get("is_authenticated", False):
            raise PermissionError("User is not authenticated")
        return func(user)
    return wrapper

@requires_auth
def view_dashboard(user):
    print("Access granted to the dashboard.")

view_dashboard({"username": "John", "is_authenticated": True})
Pitfall

Overusing decorators can make code harder to debug. Use them judiciously.


Context Managers

Context managers handle setup and cleanup tasks, ensuring proper resource management. They are commonly used with the with statement.

Basic Syntax

with open('example.txt', 'r') as file:
    content = file.read()
    print(content)
# File automatically closed after this block

Creating a Custom Context Manager

Using a Class

class MyContext:
    def __enter__(self):
        print("Entering context")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exiting context")

with MyContext() as ctx:
    print("Inside context")

Using contextlib

from contextlib import contextmanager

@contextmanager
def my_context():
    print("Entering context")
    yield
    print("Exiting context")

with my_context():
    print("Inside context")
Info

The contextlib module simplifies the creation of context managers for straightforward use cases.


Combining Decorators and Context Managers

You can combine these two features for advanced use cases, like logging the duration of context execution.

from contextlib import contextmanager
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} executed in {end_time - start_time:.2f} seconds")
        return result
    return wrapper

@timer
@contextmanager
def managed_resource():
    print("Acquiring resource")
    yield
    print("Releasing resource")

with managed_resource():
    print("Using resource")
Deep Dive

Understand the lifecycle of the with statement when using decorators with context managers for complex tasks.


Task: Practice Decorators and Context Managers

Task

Practice Task: Enhance Your Python Skills

  1. Write a Decorator:

    • Create a decorator that measures and prints the execution time of a function.

    Example:

    def timer(func):
        import time
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print(f"{func.__name__} executed in {end - start:.2f} seconds")
            return result
        return wrapper
    
  2. Create a Custom Context Manager:

    • Write a context manager that opens and closes a database connection.

    Example:

    class DatabaseConnection:
        def __enter__(self):
            print("Connecting to database")
            return self
        def __exit__(self, exc_type, exc_value, traceback):
            print("Closing database connection")
    
    with DatabaseConnection():
        print("Performing database operations")
    
  3. Combine Both Features:

    • Create a decorator that logs the use of a context manager.

    Example:

    def log_context(func):
        def wrapper(*args, **kwargs):
            print(f"Entering {func.__name__} context")
            result = func(*args, **kwargs)
            print(f"Exiting {func.__name__} context")
            return result
        return wrapper
    
    @log_context
    @contextmanager
    def sample_context():
        print("Inside context")
        yield
        print("Exiting inner context")
    
    with sample_context():
        print("Doing work")
    

Conclusion

Decorators and context managers are invaluable tools for writing Pythonic code. Practice these concepts to enhance your ability to write clean, efficient, and maintainable programs.

Copyright © 2025 Devship. All rights reserved.

Made by imParth