How do I write my own context manager in Python?
Short answer
Write a generator decorated with contextlib.contextmanager, putting setup before yield and cleanup after it. The cleanup runs even if the block raises, so it is the reliable place for teardown.
The short version
The easiest way to write a context manager is a generator decorated with @contextlib.contextmanager. Put the setup code before a single yield, and the cleanup code after it. Whatever you yield becomes the value bound by as. The cleanup after the yield runs even if the with block raises, which is exactly what makes it reliable for teardown.
A worked example
from contextlib import contextmanager
@contextmanager
def tag(name):
print(f"<{name}>") # setup
yield # the block runs here
print(f"</{name}>") # cleanup
with tag("section"):
print("content")
<section>
content
</section>
The code before yield runs on entry, the block runs where yield sits, and the code after yield runs on exit.
Yielding a value
Whatever you yield is what as binds, so a context manager can hand a resource to the block.
@contextmanager
def opened(path):
f = open(path)
try:
yield f # the block gets the file
finally:
f.close() # always closes, even on error
with opened("notes.txt") as f:
data = f.read()
Why the try/finally matters
Wrap the yield in try/finally when the cleanup must always happen. If the block raises, the exception propagates through the yield, and only a finally guarantees the cleanup still runs. Without it, an error in the block would skip the teardown and leak the resource.
What problem this solves
Context managers capture the "always do this afterwards" pattern: open then close, acquire then release, start then stop. Putting the teardown in a context manager means callers cannot forget it, because the with block runs it automatically. This is why files, locks and connections are used with with.
The class-based alternative
You can also write a context manager as a class with __enter__ and __exit__ methods. The generator form is usually shorter and clearer for simple cases, while the class form gives more control, for example to inspect or suppress an exception in __exit__. Reach for the class form when the cleanup logic needs to react to whether an error occurred.
Temporary state changes
A common real use is temporarily changing something and restoring it afterwards, such as switching a setting for the duration of a block.
@contextmanager
def temporary(obj, attr, value):
old = getattr(obj, attr)
setattr(obj, attr, value)
try:
yield
finally:
setattr(obj, attr, old) # restore no matter what
This guarantees the original value is restored even if the block raises, which is far safer than setting it back by hand after the block.