Skip to content
Hello Python
3/7

Context Managers

Topic 3 of 7, with 3 concept checks. Deterministic setup and teardown with the with statement

Guarantee cleanup around a managed operation

Managed lifetimes

Pair acquisition with release through the context manager protocol, including the exceptional path, so files, locks, and temporary state cannot leak past their intended scope.

Core lesson 01

with obj: calls obj.__enter__() before the block and obj.__exit__() after — always, even on exception — making it the idiomatic way to guarantee cleanup.

Any object implementing __enter__(self) and __exit__(self, exc_type, exc_val, exc_tb) can be used with `with`. __enter__ runs at the start of the block (its return value is what `as` binds to). __exit__ runs when the block ends for any reason — normal completion or an exception — making `with` the reliable alternative to a manual try/finally for resource cleanup.

Python example
class Resource:
    def __enter__(self):
        print("acquiring")
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("releasing")
        return False   # don't suppress exceptions

with Resource() as r:
    print("using resource")
# acquiring / using resource / releasing -- always, in that order

What to remember

What does the with statement do, and what two methods make an object a context manager?

Common footguns

  • Returning True from __exit__ by accident — this SUPPRESSES any exception raised in the block, rarely what you want unless deliberate.

Core lesson 02

Decorate a generator function with @contextmanager from contextlib; code before the single yield is setup, code after is teardown, and the yielded value is what `as` binds to.

Writing a full class with __enter__/__exit__ is often more ceremony than needed for simple setup/teardown pairs. contextlib.contextmanager lets you write it as a single generator function: code up to yield runs on entry, the yielded value becomes the `as` target, and code after yield runs on exit — wrapped in try/finally automatically so cleanup still happens if the block raises.

Python example
from contextlib import contextmanager

@contextmanager
def resource():
    print("acquiring")
    try:
        yield "the-resource"
    finally:
        print("releasing")

with resource() as r:
    print("using", r)
# acquiring / using the-resource / releasing

What to remember

How do you write a context manager using @contextmanager instead of a class?

Common footguns

  • Forgetting the try/finally around yield in your own @contextmanager function — without it, an exception in the with-block skips your cleanup code entirely.

Core lesson 03

__exit__ is always called, even on exception, and receives the exception info as arguments; returning truthy suppresses the exception, returning falsy lets it propagate.

When an exception happens inside a with block, Python calls __exit__(exc_type, exc_val, exc_tb) with the exception's details instead of (None, None, None). This lets the context manager react — log it, clean up, or suppress it entirely by returning a truthy value. If __exit__ returns anything falsy (including implicit None), the exception continues propagating after cleanup runs.

Python example
class SwallowValueErrors:
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is ValueError:
            print("suppressed:", exc_val)
            return True   # swallow it
        return False      # let anything else propagate

with SwallowValueErrors():
    raise ValueError("oops")
print("execution continues here")

What to remember

What happens to the context manager's __exit__ if an exception occurs inside the with block?

Common footguns

  • Suppressing exceptions too broadly (returning True unconditionally) — this can hide real bugs just like a bare except: would.

Python lab

Browser Python lab

Runtime · idle

Python loads on your first run. Your code stays in this browser.

Best practices

  • Prefer @contextmanager for simple setup/teardown pairs; use a class when you need extra state or methods.
  • Always use with for files, locks, and database connections instead of manual acquire/release calls.
  • Only suppress exceptions in __exit__ deliberately and narrowly — never with a bare return True.
  • Nest multiple context managers in one with statement (comma-separated) instead of deeply indented nested blocks.

Apply the concept in Interview practice

Min StackmediumLeetCode #155 · O(1) per operation

Maintain a second stack tracking the running minimum alongside the main stack, so push/pop/getMin are all O(1) — the same paired-state discipline context managers rely on for enter/exit.

Open problem
Encode and Decode TinyURLmediumLeetCode #535 · O(1) per operation

Maintain a dict mapping short codes to original URLs (and the reverse for encoding) — a state-lifecycle design exercise rather than a context-manager-specific one.

Open problem
Design Log Storage SystemmediumLeetCode #635 · O(n) per query

Store (id, timestamp) pairs and filter by truncating timestamps to the requested granularity — practice in resource/state lifecycle thinking similar to setup/teardown.

Open problem

Concept checks

Q01

What does the with statement do, and what two methods make an object a context manager?

Q02

How do you write a context manager using @contextmanager instead of a class?

Q03

What happens to the context manager's __exit__ if an exception occurs inside the with block?