Robust code and good library APIs depend on raising and handling the right errors the right way.
A full try statement has four parts: try, one or more except, an optional else that runs only if no exception occurred, and finally, which always runs (for cleanup). Putting follow-up code in else rather than in try keeps the protected region minimal, so you don't accidentally catch errors from code you didn't mean to guard.
Exceptions are classes arranged in a hierarchy rooted at BaseException. Application errors derive from Exception; KeyboardInterrupt and SystemExit deliberately don't, so except Exception won't swallow Ctrl-C. An except clause matches subclasses too, so order clauses from most to least specific. Catch several types with a tuple: except (KeyError, IndexError):.
For a library or larger application, define your own hierarchy: one base class (class PaymentError(Exception)) and specific subclasses. Callers can then catch everything from you with one clause, or a specific case.
Inside an except block, a bare raise re-raises the current exception with its traceback intact. When translating a low-level error into your own, use chaining: raise ConfigError("bad config") from err. The traceback shows both, linked by "The above exception was the direct cause...". Use from None to hide the original when it is pure noise.
Python 3.11 added e.add_note("context") to attach extra information, and exception groups: ExceptionGroup bundles several unrelated errors (for example from concurrent tasks), and except* handles each matching type within the group.
Example
class AppError(Exception):
"Base class for this application's errors."
class ConfigError(AppError):
pass
def load_port(settings):
try:
port = int(settings["port"])
except KeyError:
raise ConfigError("missing 'port'") from None
except ValueError as err:
raise ConfigError(f"bad port {settings['port']!r}") from err
else:
return port
finally:
print("checked", settings)
for s in [{"port": "8080"}, {}, {"port": "eighty"}]:
try:
print(load_port(s))
except AppError as e:
print(type(e).__name__, e, "| cause:", repr(e.__cause__))Remember
elseruns when no exception occurred;finallyalways runs- Derive custom errors from
Exceptionvia a single base class - Use
raise New(...) from errto chain, bareraiseto re-raise
Watch out for
- Putting
except Exception:before more specific clauses, making them unreachable.