Failing safely — exceptions, exception chaining, and custom exception hierarchies
Production code fails — network requests time out, files are missing, APIs return unexpected shapes. The difference between hobby code and production code is how failures are handled. Catching the wrong exception silently swallows bugs; catching too broadly hides problems; not chaining exceptions loses the root cause.
Custom exception hierarchies are how you build APIs where callers can handle errors at the right level of specificity — catching a module's base exception type without needing to know all its internal subtypes.
try/except separates the happy path from the failure path. The else block runs only when no exception occurred — it's for code that should run on success but shouldn't be inside try where its exceptions might be caught by the wrong handler. The finally block always runs — use it for guaranteed cleanup.
Python's error handling separates the happy path from failure. Each clause has a distinct role — catching specific exceptions prevents accidentally swallowing bugs you didn't intend to handle.
Catch the most specific exception type you can handle. Use raise ... from e to chain exceptions so the original traceback isn't lost. The else block prevents a handler from masking errors from unrelated code.
Defining your own exception classes lets callers catch errors at the right level — either a specific subtype or the whole module's base error — without knowing every internal detail.
Define a base exception per module. This lets you change internal exception types without breaking callers who catch the base. Always include enough context in the message to debug without a full stacktrace.