backend / python core / 12_decorators_context_managers.md

Decorators and Context Managers in Python

3 interview angles 3 min read source

Decorators and Context Managers in Python

Decorators in Python

A decorator is a function that modifies the behavior of another function or method. It is a way to wrap another function, allowing you to extend or alter its behavior without modifying its source code directly.

How Decorators Work:

  1. Definition: A decorator is a function that takes another function as an argument and returns a new function that typically extends or modifies the original function’s behavior.
  2. Syntax: Decorators are applied using the @decorator_name syntax above a function definition.
  3. Use Cases: They are commonly used for logging, authorization, memoization, timing, etc.

Example of a Decorator:

# A simple decorator
def my_decorator(func):
    def wrapper():
        print("Before the function runs")
        func()
        print("After the function runs")
    return wrapper

# Applying the decorator
@my_decorator
def greet():
    print("Hello!")

# Usage
greet()

Output:

Before the function runs
Hello!
After the function runs

In this example, my_decorator takes the greet function, wraps it in the wrapper function, and adds behavior before and after the function call.

Context Managers in Python

A context manager is used to manage resources such as files, database connections, or locks. It ensures that a certain setup is done before the block of code runs, and a cleanup action is performed afterward. Context managers are most commonly used with the with statement.

How Context Managers Work:

  1. Definition: A context manager is any object that implements the __enter__ and __exit__ methods. The __enter__ method is executed when the with block starts, and the __exit__ method is executed when the block ends, regardless of whether an exception was raised.
  2. Use Cases: Commonly used for resource management tasks like closing files or releasing locks.

Example of a Context Manager:

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

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exiting the context")
        if exc_type:
            print(f"An exception occurred: {exc_value}")

# Using the context manager with 'with'
with MyContextManager():
    print("Inside the context")
    # Uncomment to test exception handling
    # raise ValueError("An error occurred")

Output:

Entering the context
Inside the context
Exiting the context

If you uncomment the raise line inside the with block, the __exit__ method will still be called, and the exception will be handled inside it.

Key Differences between Decorators and Context Managers:

  • Purpose:

    • Decorators modify or extend the behavior of functions or methods.
    • Context managers manage resources and ensure proper setup and cleanup of resources.
  • Syntax:

    • Decorators use @decorator_name above a function.
    • Context managers use the with statement.
  • Use Case:

    • Decorators are typically used for modifying function behavior (e.g., logging, caching).
    • Context managers are used for managing resources (e.g., file handling, database connections).

Both decorators and context managers are powerful tools for managing behavior in Python, but they serve different purposes and are used in different contexts.

# A simple decorator
def my_decorator(func):
    def wrapper():
        print("Before the function runs")
        func()
        print("After the function runs")
    return wrapper

# Applying the decorator
@my_decorator
def greet():
    print("Hello!")

# Usage
greet()
class MyContextManager:
    def __enter__(self):
        print("Entering the context")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exiting the context")
        if exc_type:
            print(f"An exception occurred: {exc_value}")

# Using the context manager with 'with'
with MyContextManager():
    print("Inside the context")
    # Uncomment to test exception handling
    # raise ValueError("An error occurred")

Interview angle

  • “What is a decorator?” - a callable taking a function and returning a replacement, applied with @. Always use functools.wraps so the name, docstring and signature survive - without it, introspection and debugging break, and frameworks relying on signatures misbehave.
  • “How do you write a decorator that takes arguments?” - three levels: the outer function takes the arguments, returns the decorator, which returns the wrapper. functools.partial or a class-based decorator are alternatives.
  • “Context manager: class or @contextmanager?” - @contextmanager with try/finally around a single yield is more concise for simple setup and teardown. Use the class form with __enter__/__exit__ when you need state, reusability, or to suppress exceptions by returning True.