Context Managers
What with Actually Does
You've used with open(...) as f: since the file handling lesson. You know it automatically closes the file when the block ends. But what's actually happening?
The with statement is powered by a context manager — any object that implements two special methods: __enter__ and __exit__.
When Python executes with something as x::
- It calls
something.__enter__()— the setup phase. The return value is bound tox. - It runs the indented block.
- It calls
something.__exit__()— the teardown phase. This runs no matter what — even if an exception occurred.
That guaranteed teardown is the entire point. Resources are released, connections are closed, locks are freed — whether the block succeeded or failed.
Seeing It Directly
__enter__ runs first, returns self (so r is the object itself), the block runs, then __exit__ runs automatically. Even the variable names in __exit__ tell the story: exc_type, exc_val, exc_tb receive exception information if something went wrong.
Handling Exceptions in __exit__
__exit__ receives the exception details if the block raised one. Returning True suppresses the exception. Returning False (or None) lets it propagate normally.
The __exit__ method ran — logged the error, performed cleanup — and then let the exception continue. The try/except outside the with block caught it.
This is the power of context managers: guaranteed cleanup regardless of what happens inside the block.
A Practical Context Manager: Timed Block
with Timer() as t: measures exactly how long the block takes. t.elapsed is available after the block. The *args in __exit__ absorbs the three exception arguments when you don't need them.
contextlib — The Easier Way
Writing a full class with __enter__ and __exit__ is precise but verbose for simple cases. The contextlib module gives you a decorator that converts a generator function into a context manager:
The structure is simple:
- Everything before
yieldis the__enter__phase — setup. - The
yieldvalue is what gets bound to theasvariable. - Everything after
yield(in thefinallyblock) is the__exit__phase — teardown.
The finally ensures teardown runs even if the block raises an exception — exactly mirroring what __exit__ provides, but in a far more readable form.
A File Context Manager From Scratch
To fully understand what Python's built-in open() does, here's a simplified version:
This is almost exactly what Python's built-in open() context manager does internally. The finally guarantees the file is closed even if f.write() or f.read() raises an exception.
Nesting Context Managers
Multiple context managers can be stacked, either nested or on one line:
Or more compactly, on one line with a comma:
with section("outer"), section("inner"):
print("Doing work.")
Both are identical. The single-line form is preferred when the two contexts are closely related.
contextlib.suppress — Ignoring Specific Exceptions
A small but useful utility — a context manager that silences specified exceptions:
Without suppress, the FileNotFoundError would propagate. With it, the error is silently absorbed and execution continues. Use sparingly — suppressing exceptions hides problems — but it's the right tool when absence of a resource is genuinely not an error.
When to Write a Context Manager
The pattern that calls for a context manager is always the same: acquire something, use it, release it — and the release must happen no matter what. Common examples:
- Opening and closing files, network connections, database connections
- Acquiring and releasing locks in concurrent code
- Starting and stopping timers
- Setting up and tearing down test fixtures
- Temporarily changing configuration (then restoring the original)
If you find yourself writing try / finally to guarantee cleanup, a context manager is almost always the cleaner solution.
What You Have Learned
Context managers formalise the acquire-use-release pattern into a reusable, composable structure with guaranteed cleanup.
The key ideas:
with obj as x:callsobj.__enter__()thenobj.__exit__()— always, even on exceptions__exit__receives exception info; returningTruesuppresses the exception@contextmanagerfromcontextlibconverts a generator function into a context manager- Everything before
yieldis setup; everything after (infinally) is teardown - The
yieldvalue is what theasvariable receives contextlib.suppress(ExceptionType)silently ignores specified exceptions- Use context managers whenever cleanup must be guaranteed
In the next lesson, you'll learn about dunder methods — how to make your own objects behave like Python built-ins, supporting len(), indexing, comparison operators, and more.