Exception Handling
Handle failures with try/except, custom exceptions, and context managers.
Try/Except/Else/Finally
try runs risky code; except catches specific exception types; else runs when no exception occurred; finally always runs for cleanup.
Catch specific exceptions—bare except catches KeyboardInterrupt unintentionally. Use except ValueError as err to inspect messages and chains.
Raise exceptions with raise NewError("msg") from err to preserve cause chains in logs.
- Never use exceptions for normal control flow on hot paths
- Define domain exception hierarchies for API layers
- Log stack traces on unexpected errors only
try:
data = parse_payload(raw)
except ValidationError as err:
logger.warning("invalid payload", exc_info=err)
raise HTTPException(400, str(err)) from errContext Managers
with open(...) as f ensures files close. contextlib.contextmanager builds managers from generators with yield.
Custom managers implement __enter__/__exit__ or use @contextmanager decorator. __exit__ receives exception info and can suppress errors by returning True.
Async context managers use async with for database pools and HTTP clients.
- Prefer with over manual close in finally
- Use closing() from contextlib for objects with close()
- Test context managers release resources on exceptions
@contextmanager
def timer(label):
start = time.perf_counter()
try:
yield
finally:
print(f"{label}: {time.perf_counter() - start:.3f}s")