Python context managers are the language’s answer to the question “how do I guarantee cleanup even when an exception occurs?” The with statement calls __enter__ on entry and __exit__ on exit — even if an exception is raised. This makes resource management both safe and readable.
⚡ TL;DR: with statement calls __enter__ on entry, __exit__ on exit (even on exception). Implement via __enter__/__exit__ methods or @contextmanager decorator. Use contextlib.suppress to ignore specific exceptions. ExitStack for dynamic context managers. Multiple managers in one with statement.
__enter__ and __exit__
class DatabaseConnection:
def __init__(self, dsn): self.dsn = dsn
def __enter__(self):
self.conn = create_connection(self.dsn)
return self.conn # Value bound to 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type: # Exception occurred
self.conn.rollback()
else:
self.conn.commit()
self.conn.close()
return False # False = don't suppress exception
# Return True to suppress the exception
with DatabaseConnection('postgresql://...') as conn:
conn.execute('INSERT INTO ...')
# Auto-commits or rolls back, always closes
@contextmanager — the easy way
from contextlib import contextmanager
import time
@contextmanager
def timer(name=''):
start = time.perf_counter()
try:
yield # Code inside 'with' block runs here
finally: # Runs even on exception
elapsed = (time.perf_counter()-start)*1000
print(f'{name}: {elapsed:.1f}ms')
with timer('database query'):
results = db.execute('SELECT ...') # Timed!
# Transaction context manager:
@contextmanager
def transaction(db):
try:
yield db
db.commit()
except:
db.rollback()
raise # Re-raise after rollback
Multiple and nested managers
# Multiple managers in one with:
with open('input.txt') as f_in, open('output.txt','w') as f_out:
f_out.write(f_in.read())
# Both files closed even if exception!
# contextlib.suppress — ignore specific exceptions:
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('/tmp/temp_file') # OK if file doesn't exist
# Same as: try/except FileNotFoundError: pass (but cleaner)
contextlib.ExitStack — dynamic managers
from contextlib import ExitStack
# Open variable number of files:
file_names = ['a.txt','b.txt','c.txt']
with ExitStack() as stack:
files = [stack.enter_context(open(f)) for f in file_names]
# All files open, all will be closed on exit
data = [f.read() for f in files]
# ExitStack.callback: register any cleanup function
with ExitStack() as stack:
conn = db.connect()
stack.callback(conn.close) # Always closes
stack.callback(conn.rollback) # Always rolls back (if not committed)
- ✅ @contextmanager for simple context managers (no class needed)
- ✅ Multiple managers in one with statement
- ✅ contextlib.suppress instead of empty except blocks
- ✅ ExitStack for dynamic or variable numbers of managers
- ❌ Never return True from __exit__ unless you intentionally suppress exceptions
- ❌ Never put cleanup in finally when with handles it
External reference: Python contextlib documentation.
Recommended Reading
→ Designing Data-Intensive Applications — The bible of distributed systems and production engineering at scale.
→ The Pragmatic Programmer — Timeless engineering wisdom every senior developer needs.
Affiliate links. We earn a small commission at no extra cost to you.
Free Weekly Newsletter
🚀 Join 2,000+ Senior Developers
Get expert-level JavaScript, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
