By WebNest Studio Engineering Team
Python Tutorial
Python Context Managers and the with Statement
A context manager is an object that defines what should happen when entering and leaving a block of code, and it is used through the with statement. The most common example is file handling: with open("file.txt") as f: guarantees the file gets closed automatically when the block ends, even if an exception occurs inside it — something that's easy to forget when managing a file handle manually with try/finally.
Context managers exist to make "setup, use, then guaranteed cleanup" patterns concise and reliable, wherever that pattern applies: files, network connections, database transactions, locks, or temporarily changed settings.
Why it matters
Files, database connections, locks and network sockets must all be released when you have finished with them, including when an error interrupts the code. The with statement guarantees it. Writing your own context manager lets you give the same guarantee for anything that has a set-up and a clean-up step.
The with Statement and __enter__/__exit__
Any object that defines both __enter__ and __exit__ methods can be used with the with statement. When the with block begins, Python calls __enter__, and whatever it returns is bound to the variable after as (if present). When the block ends — normally or due to an exception — Python calls __exit__, passing it details about any exception that occurred (or three None values if the block completed normally). If __exit__ returns True, any exception that occurred inside the block is suppressed; if it returns a falsy value (the default), the exception propagates normally after cleanup runs.
Writing a Context Manager with contextlib.contextmanager
Writing a full class with __enter__ and __exit__ is sometimes more ceremony than necessary for a simple case. The contextlib.contextmanager decorator lets you write a context manager as a single generator function instead: code before the yield statement acts as __enter__, the yielded value becomes what's bound after as, and code after the yield (typically inside a finally block) acts as __exit__, running even if the caller's code inside the with block raises an exception.
Why This Matters for Resource Safety
Resources like open files, network sockets, and database connections are typically limited and must be explicitly released. Relying on a programmer to remember a matching "close" call every time is fragile — an exception raised between "open" and "close" would skip the cleanup entirely unless it's wrapped in try/finally. Context managers move that responsibility into the object itself, so correct cleanup happens automatically every time the with block is used, regardless of how the block exits.
Examples
A class-based context manager with __enter__ and __exit__
class ManagedFile:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, traceback):
self.file.close()
print("File closed automatically.")
return False # do not suppress exceptions
with ManagedFile("notes.txt", "w") as f:
f.write("Hello, context managers!")
print("Block finished.")
File closed automatically.
Block finished.
The same behavior written with contextlib.contextmanager
from contextlib import contextmanager
@contextmanager
def managed_file(filename, mode):
f = open(filename, mode)
try:
yield f # code before yield = __enter__, f is bound to "as f"
finally:
f.close() # code after yield = __exit__, always runs
print("File closed automatically.")
with managed_file("notes.txt", "w") as f:
f.write("Hello again!")
print("Block finished.")
File closed automatically.
Block finished.
Common Mistakes
- Manually calling open() and close() without try/finally, so an exception between the two calls leaves the file (or other resource) unreleased.
- Forgetting the try/finally inside a contextlib.contextmanager generator, so cleanup after yield never runs if the with block raises an exception.
- Returning True from __exit__ unintentionally, which silently swallows exceptions that should have propagated and been visible.
- Assuming a with block's variable ("as f") still contains a fully open, usable resource after the block ends — it has already been cleaned up by __exit__.
Exercise
Using contextlib.contextmanager, write a context manager tag that prints an opening tag before the block runs and a closing tag after it.
Expected output: <b>, hello, </b> (one per line)
from contextlib import contextmanager
# TODO: a context manager tag(name) that prints <name>, runs the block, then prints </name>
with tag("b"):
print("hello")
Show hints
- Decorate a generator function with
@contextmanager. The code beforeyieldruns on entry and the code after it on exit. - Put the
yieldin atryblock and the closing print infinally, so it runs even if the block raises.
Show solution
from contextlib import contextmanager
@contextmanager
def tag(name):
print(f"<{name}>")
try:
yield
finally:
print(f"</{name}>")
with tag("b"):
print("hello")
Quick quiz
1. Which two methods does a class need in order to be used in a with statement?
- A. __open__ and __close__
- B. __start__ and __stop__
- C. __enter__ and __exit__
- D. __init__ and __del__
Show answer
C. __enter__ runs at the start of the block and __exit__ at the end.
2. Is __exit__ called when the block raises an exception?
- A. No
- B. Only if there is an except block
- C. Only for ValueError
- D. Yes; it receives the details of the exception
Show answer
D. That is what makes the clean-up reliable.
3. What happens if __exit__ returns True?
- A. The block runs again
- B. The exception raised in the block is suppressed
- C. The program exits
- D. Nothing
Show answer
B. Returning a falsy value lets the exception continue to propagate.
Interview questions
What is a context manager?
Show answer
An object that defines what happens on entering and on leaving a with block, through its __enter__ and __exit__ methods. It is used wherever a resource is acquired and must be released: files, locks, database transactions, temporary changes of settings. The release happens even if the block raises an exception.
What are the two ways to write a context manager?
Show answer
As a class with __enter__, which returns the value bound by as, and __exit__, which performs the clean-up. Or as a generator function decorated with @contextlib.contextmanager: the code before yield is the set-up, the yielded value is bound by as, and the code after it, placed in a finally block, is the clean-up. The generator form is shorter for simple cases.
Key Points to Remember
- The with statement guarantees cleanup by calling __enter__ on entry and __exit__ on exit, even when an exception occurs.
- __exit__ receives exception details and can suppress the exception by returning True (the default, falsy return, lets it propagate).
- contextlib.contextmanager turns a single generator function into a context manager: code before yield is setup, code after (in finally) is cleanup.
- Context managers make resource cleanup automatic and reliable, replacing manual and error-prone try/finally patterns.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs Python in your browser. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering