A context manager is an object that sets something up at the start of a with block and reliably cleans it up at the end, even if the block raises an exception. Files are the best-known example:
with open("report.txt", "w", encoding="utf-8") as file:
file.write("Clear skies\n")
# the file is closed here, whatever happened inside the blockYou can write your own context managers for anything with a "start" and "finish": opening and closing a connection, acquiring and releasing a lock, starting and stopping a timer, or temporarily changing a setting. This lesson explains the protocol behind with, how exceptions reach __exit__, what the as name receives, and the shortcut @contextmanager decorator. The examples follow an observatory opening and closing its dome.
Resource lifetime
Some resources must be released after use: files, network connections, database transactions, locks and temporary directories. Without with, you need try/finally to be sure the release happens:
file = open("log.txt", "a", encoding="utf-8")
try:
file.write("Session started\n")
finally:
file.close()with packages that pattern into one line and makes it impossible to forget:
with open("log.txt", "a", encoding="utf-8") as file:
file.write("Session started\n")The clean-up runs when the block finishes normally, when it raises an exception, and when it exits early through return, break or continue. Several managers can share one statement, with open(a) as src, open(b, "w") as dst:, and they are cleaned up in reverse order. From Python 3.10 you can wrap a long list of managers in brackets across several lines.
Protocol methods
Any object with two special methods is a context manager:
__enter__(self)runs at the start of the block. Its return value is bound to the name afteras.__exit__(self, exc_type, exc_value, traceback)runs at the end. If the block raised an exception, the three arguments describe it; otherwise all three areNone.
class ObservingSession:
def __enter__(self):
print("Open dome")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Close dome")
return False # don't suppress exceptionsIf __exit__ returns a truthy value, Python treats the exception as handled and continues after the with block. Returning False or None lets it propagate as normal. Suppressing exceptions should be deliberate and narrow; a manager that returns True for everything hides bugs. contextlib.suppress(FileNotFoundError) is the standard tool when you really do want to ignore one specific error.
Bound value
The name after as receives whatever __enter__ returns, which is not necessarily the manager itself:
class Telescope:
def __init__(self, target):
self.target = target
def __enter__(self):
print("Pointing telescope")
return self.target # bind the target, not the Telescope
def __exit__(self, *exc):
print("Parking telescope")
with Telescope("Jupiter") as target:
print("Observing", target)open() returns a file object whose __enter__ returns itself, which is why with open(...) as f gives you the file. A database connection's __enter__, on the other hand, may return a cursor or a transaction. Check the documentation of each manager, and remember that as is optional: with lock: is perfectly valid when you don't need a name.
Generator managers
Writing a class with two methods is verbose for simple cases. contextlib.contextmanager turns a generator function into a context manager: code before yield is the set-up, the yielded value is bound by as, and code after yield is the clean-up.
from contextlib import contextmanager
@contextmanager
def observing_session(target):
print("Open dome")
try:
yield target
finally:
print("Close dome")If the with block raises an exception, it is re-raised at the yield inside your generator. That's why essential clean-up belongs in a finally around the yield: without it, an exception would skip the clean-up line entirely. The generator must yield exactly once; yielding twice, or not at all, raises RuntimeError. To handle the exception rather than just clean up, wrap the yield in try/except.
Independent practice and a worked check
Task: verify that an observing manager closes after successful work and after a raised exception.
from contextlib import contextmanager
log = []
@contextmanager
def observing_session(target):
log.append(f"open {target}")
try:
yield target
finally:
log.append(f"close {target}")
with observing_session("Venus"):
log.append("observe Venus")
try:
with observing_session("Mars"):
raise RuntimeError("tracking motor failed")
except RuntimeError as error:
log.append(f"error: {error}")
print(log)['open Venus', 'observe Venus', 'close Venus', 'open Mars', 'close Mars', 'error: tracking motor failed']"close Mars" appears before the error is handled outside, proving the clean-up ran first. For the worked check, essential @contextmanager clean-up goes in finally around yield.
Reference and next practice
The language reference explains the with statement, and contextlib documents contextmanager, suppress and ExitStack. Continue with the file lifecycle and revisit error handling. Practise with the Python exercises and Python quiz.
Context Managers Summary
- Resource lifetime: with pairs context entry with cleanup when leaving the block.
- Protocol methods: __enter__ prepares the context and __exit__ handles normal and exceptional exits.
- Bound value: The result of __enter__ is assigned to the name after as.
- Generator managers: contextmanager divides setup and cleanup around one yield.
Context Managers Examples
Each example shows the code, the output it produces and what each part does.
Context Managers in a small program
from contextlib import contextmanager
@contextmanager
def observing_session():
print("Open dome")
try:
yield "Jupiter"
finally:
print("Close dome")
with observing_session() as target:
print(target)Open dome
Jupiter
Close domeLine-by-line explanation
- Lines 3 to 9Code before
yieldis the set-up; thefinallyclause is the clean-up. - Line 7The yielded value, "Jupiter", is what
as targetreceives. - Lines 11 to 12The block runs between the set-up and the clean-up.
Clean-up after an exception
class ObservingSession:
def __init__(self, target):
self.target = target
def __enter__(self):
print(f"Open dome for {self.target}")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Close dome")
if exc_type is ValueError:
print(f"Logged problem: {exc_value}")
return True # handled: don't re-raise
return False
with ObservingSession("Saturn") as session:
print("Observing", session.target)
with ObservingSession("Mars") as session:
raise ValueError("cloud cover")
print("Program continues")Open dome for Saturn
Observing Saturn
Close dome
Open dome for Mars
Close dome
Logged problem: cloud cover
Program continuesLine-by-line explanation
- Lines 9 to 14
__exit__always runs. It suppresses onlyValueError, and only because it returnsTruefor that type. - Lines 19 to 20The exception interrupts the block, but the dome still closes.
- Line 21Because the error was handled, execution continues after the
withblock.
Common Context Managers Mistakes
Putting clean-up after yield without finally
@contextmanager
def session():
print("Open dome")
yield
print("Close dome") # skipped if the block raises@contextmanager
def session():
print("Open dome")
try:
yield
finally:
print("Close dome")An exception in the block is raised at the yield, so only a finally guarantees the clean-up.
Returning True from __exit__ for every error
def __exit__(self, exc_type, exc_value, traceback):
self.close()
return True # silently hides every exception, including bugsdef __exit__(self, exc_type, exc_value, traceback):
self.close()
return False # let exceptions propagateContext Managers in Real Projects
Context managers you will meet
The standard library is full of them: open(), threading.Lock(), tempfile.TemporaryDirectory(), decimal.localcontext(), unittest.mock.patch() and contextlib.redirect_stdout(). Database libraries use with connection: to commit a transaction on success and roll it back on an exception. Testing frameworks use with pytest.raises(ValueError): to assert that a block fails in the expected way.
In your own code, a context manager is worth writing whenever two actions must always come as a pair. contextlib.ExitStack handles a variable number of resources, such as opening every file in a list, and makes sure all of them are closed.
Context Managers FAQ
What is a context manager in Python?
An object that defines __enter__ and __exit__, so it can be used in a with statement. __enter__ runs when the block starts and __exit__ runs when it ends, even if an exception occurred, which makes context managers ideal for releasing resources.
Why use with open() instead of open() and close()?
with closes the file automatically, including when an exception is raised or the function returns early. With manual close(), an error between opening and closing can leave the file open, and buffered data may never be written.
Does __exit__ run if there is an exception?
Yes. __exit__ receives the exception's type, value and traceback. It runs before the exception continues upwards, and if it returns a truthy value the exception is suppressed.
How do I write a context manager with a function?
Decorate a generator function with @contextlib.contextmanager. Put the set-up before a single yield, yield the value for as, and put clean-up in a finally block after it.
Can I use several context managers in one with statement?
Yes: with open("a.txt") as a, open("b.txt", "w") as b:. They are entered left to right and exited in reverse order. From Python 3.10 you can put the managers in brackets and spread them over several lines.
Context Managers Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Where should essential contextmanager cleanup go?
Which review concern is valid for context managers?
Context Managers Quiz
2 questions to check what you learned.
Where should essential contextmanager cleanup go?
- Only before yield
- In finally around yield
- Python ignores the distinction
Which statement correctly describes bound value?
- The result of __enter__ is assigned to the name after as.
- Only the example output matters, not the rule
- Every operation has the same behavior