You've used with open(...) as f: already. The with statement is a general protocol — you can write your own. Easiest way: @contextmanager from contextlib:
from contextlib import contextmanager
@contextmanager
def stash():
items = []
print("enter")
try:
yield items
finally:
print(f"exit, len={len(items)}")
with stash() as bag:
bag.append(1)
bag.append(2)Output:
enter
exit, len=2
It's a generator that yields once?
Right — exactly one yield. Everything before the yield is the setup (runs at with entry). The yielded value is what as bag binds to. Everything after the yield is the teardown (runs when the with block exits — even if an exception was raised). The try / finally makes sure cleanup runs in both cases.
Why use a context manager instead of just plain setup/cleanup?
Three reasons. (1) The cleanup is guaranteed — even if the with body raises. (2) The pairing is visually obvious — setup at top, cleanup at bottom of the with block. (3) It composes — one with block can cover multiple resources.
When would I write one?
Anytime you have setup-then-teardown. Open and close a temp directory. Acquire and release a lock. Start and stop a timer. Patch and unpatch a global. The pattern is: "do this thing, no matter what, when we leave." If you find yourself writing try / finally to clean something up, a context manager is usually the cleaner shape.
@contextmanager — easy custom with-blocksfrom contextlib import contextmanager
@contextmanager
def resource():
# setup
print("acquire")
try:
yield "the-value"
finally:
# teardown — runs even on exception
print("release")
with resource() as r:
print(r)
# acquire
# the-value
# release| Code position | When it runs | Purpose |
|---|---|---|
Before yield | at with entry | setup (open file, acquire lock, start timer) |
The yield value | hands value to as ... | what the body sees |
After yield (in finally) | at with exit | teardown (close, release, stop) |
try / finally@contextmanager
def stash():
items = []
try:
yield items
finally:
print(f"exit, len={len(items)}")Without finally, an exception inside the with body would skip the cleanup. The try / finally guarantees it runs both on success and on exception.
# Without context manager — easy to forget cleanup
start_time = time.time()
do_work()
elapsed = time.time() - start_time # never runs if do_work raises
# With context manager — guaranteed
with timer() as t:
do_work()
# elapsed always reported, even on exception| Manager | Setup → Teardown |
|---|---|
open("f") | open file → close file |
threading.Lock() | acquire → release |
decimal.localcontext() | save context → restore context |
unittest.mock.patch("x") | replace x → restore x |
withYou can stack them:
with open("a") as a, open("b") as b:
...
# both files closed at exit, in reverse order@contextmanager isn't enoughFor most cases the decorator is fine. The full version — a class with __enter__ and __exit__ methods — is needed when you want to handle exceptions specially or when the manager has lots of state:
class MyManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
... # return True to swallow the exceptionWeek 4 won't go into the class form. The decorator covers 90% of real cases.
You've used with open(...) as f: already. The with statement is a general protocol — you can write your own. Easiest way: @contextmanager from contextlib:
from contextlib import contextmanager
@contextmanager
def stash():
items = []
print("enter")
try:
yield items
finally:
print(f"exit, len={len(items)}")
with stash() as bag:
bag.append(1)
bag.append(2)Output:
enter
exit, len=2
It's a generator that yields once?
Right — exactly one yield. Everything before the yield is the setup (runs at with entry). The yielded value is what as bag binds to. Everything after the yield is the teardown (runs when the with block exits — even if an exception was raised). The try / finally makes sure cleanup runs in both cases.
Why use a context manager instead of just plain setup/cleanup?
Three reasons. (1) The cleanup is guaranteed — even if the with body raises. (2) The pairing is visually obvious — setup at top, cleanup at bottom of the with block. (3) It composes — one with block can cover multiple resources.
When would I write one?
Anytime you have setup-then-teardown. Open and close a temp directory. Acquire and release a lock. Start and stop a timer. Patch and unpatch a global. The pattern is: "do this thing, no matter what, when we leave." If you find yourself writing try / finally to clean something up, a context manager is usually the cleaner shape.
@contextmanager — easy custom with-blocksfrom contextlib import contextmanager
@contextmanager
def resource():
# setup
print("acquire")
try:
yield "the-value"
finally:
# teardown — runs even on exception
print("release")
with resource() as r:
print(r)
# acquire
# the-value
# release| Code position | When it runs | Purpose |
|---|---|---|
Before yield | at with entry | setup (open file, acquire lock, start timer) |
The yield value | hands value to as ... | what the body sees |
After yield (in finally) | at with exit | teardown (close, release, stop) |
try / finally@contextmanager
def stash():
items = []
try:
yield items
finally:
print(f"exit, len={len(items)}")Without finally, an exception inside the with body would skip the cleanup. The try / finally guarantees it runs both on success and on exception.
# Without context manager — easy to forget cleanup
start_time = time.time()
do_work()
elapsed = time.time() - start_time # never runs if do_work raises
# With context manager — guaranteed
with timer() as t:
do_work()
# elapsed always reported, even on exception| Manager | Setup → Teardown |
|---|---|
open("f") | open file → close file |
threading.Lock() | acquire → release |
decimal.localcontext() | save context → restore context |
unittest.mock.patch("x") | replace x → restore x |
withYou can stack them:
with open("a") as a, open("b") as b:
...
# both files closed at exit, in reverse order@contextmanager isn't enoughFor most cases the decorator is fine. The full version — a class with __enter__ and __exit__ methods — is needed when you want to handle exceptions specially or when the manager has lots of state:
class MyManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
... # return True to swallow the exceptionWeek 4 won't go into the class form. The decorator covers 90% of real cases.
Create a free account to get started. Paid plans unlock all tracks.