Handle recoverable failures (network timeout, JSON parse, missing key) without crashing the program.
try/except is a safety net under a tightrope; raise is shouting up the ladder; the ladder is the call stack.
try:
io.read() ---+
| raises OSError
v
except OSError as e:
handle(e)
raise DomainError('io failed') from e
finally:
cleanup() # ALWAYS runs
Python’s exception model is EAFP - Easier to Ask Forgiveness than Permission. Use try: x[k] except KeyError: rather than if k in d: (which races on concurrent mutation and does two lookups). Catch specific exceptions (not bare except:) so you don’t swallow KeyboardInterrupt/SystemExit. Use raise X from Y (PEP 3134) to preserve the cause chain.
try:
with open('data.json') as f:
cfg = json.load(f)
except FileNotFoundError as e:
cfg = {}
log.info('defaults', path=e.filename)
specific except; bare except: would also catch KeyboardInterrupt.
try:
result = db.execute(q)
except psycopg2.OperationalError as e:
raise ServiceUnavailable('db down') from e
convert internal exceptions to API exceptions; from e preserves chain.
import contextlib
with contextlib.suppress(FileNotFoundError):
os.remove('cache.tmp') # ok if gone
contextlib.suppress for ‘I know this raises and I’m fine with that’.
Swallowing with except: pass masks real bugs; bare except: catches KeyboardInterrupt/SystemExit too. Catching broad Exception and not re-raising leaves processes in inconsistent state.
Specific exceptions; raise X from e at boundaries; never bare except/pass; finally for cleanup; with-statements for resources.
except Exception: pass after upload hid the failure.except: pass; unit test asserts exception.ServiceUnavailable; no traceback to the DB failure.raise ServiceUnavailable('db down') without from e.raise ServiceUnavailable('db down') from e.