Gyaan

Context Managers

intermediate context-manager with enter-exit contextlib

A context manager is an object that sets something up and guarantees it gets cleaned up — no matter what happens in between. Think of it like a hotel check-in: we arrive (__enter__), stay and do our thing, and the hotel ensures checkout happens (__exit__) even if there’s a fire alarm.

The Problem Context Managers Solve

Without context managers, we have to remember to clean up resources manually. And if an exception happens before cleanup, we’re in trouble.

# Without context manager — risky
f = open("data.txt")
content = f.read()
f.close()  # what if an error happens before this line?

With a context manager, cleanup is guaranteed:

# With context manager — safe
with open("data.txt") as f:
    content = f.read()
# f.close() happens automatically, even if an error occurs

How It Works: enter and exit

The with statement calls two special methods on the object:

  1. __enter__() — runs at the start, returns something we can use (the as variable)
  2. __exit__() — runs at the end, handles cleanup and any exceptions
with MyManager() as obj:
Step 1 __enter__() called → returns obj
Step 2 Our code block runs (the indented body)
If an exception happens here → still goes to Step 3
↓ always runs (even on exception)
Step 3 __exit__(exc_type, exc_val, exc_tb) called → cleanup
If __exit__ returns True → exception is suppressed. Otherwise → re-raised.

Writing a Class-Based Context Manager

We just need a class with __enter__ and __exit__ methods.

class Timer:
    def __enter__(self):
        import time
        self.start = time.time()
        return self  # this becomes the 'as' variable

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        elapsed = time.time() - self.start
        print(f"Took {elapsed:.2f} seconds")
        return False  # don't suppress exceptions

with Timer():
    total = sum(range(1_000_000))
# Prints: Took 0.03 seconds

The __exit__ method receives three arguments about any exception that occurred. If no exception, all three are None.

The @contextmanager Decorator

Writing a whole class just for setup/teardown can feel heavy. The contextlib module gives us a decorator that turns a generator function into a context manager.

from contextlib import contextmanager

@contextmanager
def timer():
    import time
    start = time.time()
    yield  # everything before yield = __enter__, after = __exit__
    elapsed = time.time() - start
    print(f"Took {elapsed:.2f} seconds")

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

Everything before yield is the setup (__enter__). Everything after yield is the cleanup (__exit__). If we need to return a value, we yield it.

@contextmanager
def open_db():
    conn = create_connection()
    try:
        yield conn        # caller gets the connection
    finally:
        conn.close()      # cleanup always happens

Common Uses

Context managers pop up everywhere in Python:

  • File handlingwith open(...) as f
  • Database connectionswith db.connect() as conn
  • Lockswith threading.Lock()
  • Temporary directorieswith tempfile.TemporaryDirectory() as d
  • Suppressing exceptionswith contextlib.suppress(FileNotFoundError)

Nested with Statements

We can nest them or use a single with for multiple managers:

# Both are equivalent
with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

In simple language, context managers are Python’s way of saying “I’ll handle the cleanup, no matter what.” We just focus on the work inside the with block, and Python takes care of the rest.