with
try/finally in one line: the manager’s __enter__ runs before the block, its __exit__ runs after it — on success, on an exception, on return.
with open(path) as f: data = f.read()
with A() as a, B() as b: body
with ( A() as a, B() as b, ): body
x = mgr.__enter__() try: body finally: mgr.__exit__(...)
Demo
log = [] class Tag: def __init__(self, name): self.name = name def __enter__(self): log.append('enter ' + self.name) return self def __exit__(self, exc_type, exc, tb): log.append('exit ' + self.name) try: with Tag('outer'), Tag('inner'): log.append(100 // 4) except ZeroDivisionError: log.append('caught outside') log
In the order tab, "exit inner" comes before "exit outer" in both cases, and before the outer except sees the error — cleanup happens first, handling after. In the __exit__ returns True tab, n = 0 skips "rest of body" but the program carries on: the ZeroDivisionError was swallowed. Any other exception (a NameError, say) gets False back and escapes. In the @contextmanager tab the try/finally around yield is what makes cleanup run on errors.
Syntax slots
| Name | Type | Required | Description |
|---|---|---|---|
| manager | expression | yes | Evaluated once. Its type must define __enter__ and __exit__ (a context manager). |
| as name | name / pattern | no | Bound to the return value of __enter__() — not necessarily the manager itself. Still bound after the block. |
| body | block | yes | Runs between __enter__ and __exit__. However it ends, __exit__ is called. |
Common patterns
with open('config.toml', encoding='utf-8') as f: text = f.read()
with lock: counter += 1
from contextlib import suppress with suppress(FileNotFoundError): os.remove('cache.tmp')
from contextlib import contextmanager @contextmanager def timer(label): start = time.perf_counter() try: yield finally: print(label, time.perf_counter() - start)
Examples
Pitfalls
with open('data.txt', 'w') as f: f.write('abc') with open('data.txt') as f: pass f.read()
with open('data.txt', 'w') as f: f.write('abc') with open('data.txt') as f: text = f.read() text
class Timer: def __enter__(self): return self def __exit__(self, *exc): print('timer stopped') return True with Timer(): 1 / 0 print('error vanished')
class Timer: def __enter__(self): return self def __exit__(self, *exc): print('timer stopped') with Timer(): 1 / 0 print('error vanished')
from contextlib import contextmanager @contextmanager def tag(): print('open') yield print('close') with tag(): 1 / 0
from contextlib import contextmanager @contextmanager def tag(): print('open') try: yield finally: print('close') with tag(): 1 / 0
When to use
- Any resource that must be released: files, sockets, locks, DB connections and transactions
- Temporarily changing state and restoring it (decimal.localcontext, unittest.mock.patch, os.chdir helpers)
- Ignoring one specific exception (contextlib.suppress)
- Catching and handling an error → try / except
- Objects without __enter__/__exit__ → wrap them with contextlib.closing() or write a small manager
Notes
FAQ
It opens the file, binds it to the as name, and closes it when the block ends — also if the block raises or returns. It replaces f = open(...); try: ...; finally: f.close().