#1 Data Analytics Program in India
₹2,499₹1,499Enroll Now
7 min read
•Question 22 of 41medium

Context Managers

Using with statement effectively.

What You'll Learn

  • What context managers are and why they matter
  • The with statement protocol
  • Creating class-based context managers
  • Using @contextmanager decorator
  • Exception handling in context managers

Why Context Managers?

Context managers ensure resources are properly acquired and released, even when exceptions occur. They prevent resource leaks (file handles, database connections, locks).

code.pyPython
# Without context manager (problematic)
f = open("file.txt")
try:
    data = f.read()
finally:
    f.close()  # Must remember to close!

# With context manager (safe and clean)
with open("file.txt") as f:
    data = f.read()
# File automatically closed, even if exception occurs

The Context Manager Protocol

A context manager implements __enter__ and __exit__:

code.pyPython
class ManagedResource:
    def __init__(self, name):
        self.name = name
        print(f"Creating {name}")

    def __enter__(self):
        print(f"Entering {self.name}")
        return self  # Returned value is bound to 'as' variable

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Exiting {self.name}")
        # exc_type, exc_val, exc_tb: exception info (or None)
        # Return True to suppress exception, False to propagate
        return False

with ManagedResource("MyResource") as resource:
    print(f"Using {resource.name}")

# Output:
# Creating MyResource
# Entering MyResource
# Using MyResource
# Exiting MyResource

Practical Examples

Timer Context Manager

code.pyPython
import time

class Timer:
    def __enter__(self):
        self.start = time.perf_counter()
        return self

    def __exit__(self, *args):
        self.elapsed = time.perf_counter() - self.start
        print(f"Elapsed: {self.elapsed:.4f} seconds")
        return False

with Timer() as t:
    sum(range(1_000_000))
# Elapsed: 0.0312 seconds

# Access timing after block
print(f"Operation took {t.elapsed:.4f}s")

Database Transaction Manager

code.pyPython
class Transaction:
    def __init__(self, connection):
        self.connection = connection

    def __enter__(self):
        self.connection.begin()
        return self.connection

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.connection.commit()
            print("Transaction committed")
        else:
            self.connection.rollback()
            print(f"Transaction rolled back: {exc_val}")
        return False  # Don't suppress exceptions

# Usage
with Transaction(db_conn) as conn:
    conn.execute("INSERT INTO users ...")
    conn.execute("UPDATE accounts ...")
# Auto-commits on success, rolls back on exception

Using contextlib

The contextlib module provides utilities to simplify context managers:

@contextmanager Decorator

code.pyPython
from contextlib import contextmanager

@contextmanager
def timer():
    start = time.perf_counter()
    try:
        yield  # Code in 'with' block runs here
    finally:
        elapsed = time.perf_counter() - start
        print(f"Elapsed: {elapsed:.4f}s")

with timer():
    sum(range(1_000_000))

# With return value
@contextmanager
def temp_directory():
    import tempfile
    import shutil

    path = tempfile.mkdtemp()
    try:
        yield path  # Value after 'as'
    finally:
        shutil.rmtree(path)

with temp_directory() as tmpdir:
    print(f"Working in {tmpdir}")
# Directory cleaned up automatically

Other contextlib Utilities

code.pyPython
from contextlib import suppress, redirect_stdout, closing
import io

# suppress - ignore specific exceptions
with suppress(FileNotFoundError, PermissionError):
    os.remove("maybe_missing.txt")
# No error if file doesn't exist

# redirect_stdout - capture print output
f = io.StringIO()
with redirect_stdout(f):
    print("Hello, World!")
output = f.getvalue()  # "Hello, World!\n"

# closing - call .close() on exit
from urllib.request import urlopen

with closing(urlopen("https://example.com")) as page:
    content = page.read()

Multiple Context Managers

code.pyPython
# Multiple in one line
with open("in.txt") as f_in, open("out.txt", "w") as f_out:
    f_out.write(f_in.read())

# Parentheses for multiple lines (Python 3.10+)
with (
    open("file1.txt") as f1,
    open("file2.txt") as f2,
    open("file3.txt") as f3
):
    pass

# ExitStack for dynamic number
from contextlib import ExitStack

filenames = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
    files = [stack.enter_context(open(f)) for f in filenames]
    # All files properly closed on exit

Exception Handling in exit

code.pyPython
class ExceptionHandler:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is ValueError:
            print(f"Caught ValueError: {exc_val}")
            return True  # Suppress the exception
        return False  # Propagate other exceptions

with ExceptionHandler():
    raise ValueError("This is suppressed")
print("Continues execution")

with ExceptionHandler():
    raise TypeError("This propagates")  # Will raise!

Interview Tip

When asked about context managers:

  1. They manage setup/teardown with guaranteed cleanup
  2. enter acquires resources, exit releases them
  3. Use @contextmanager for simple cases
  4. exit returning True suppresses exceptions
  5. Common uses: files, locks, DB transactions, temp resources