Errors#
Python signals failure with exceptions. Every error you’ve seen
(ValueError, KeyError, FileNotFoundError) is a class
that inherits from BaseException. You raise them, you catch
them, you let them propagate.
Catching#
The full structure.
try:
risky()
except (ValueError, KeyError) as e:
handle(e)
except OSError:
give_up()
else:
# ran only if no exception was raised
commit()
finally:
# always runs (cleanup)
close()
Catch what you actually expect. Catching Exception (or worse,
BaseException) hides bugs.
EAFP, “Easier to Ask Forgiveness than Permission”, is the standard Python style.
# idiomatic
try:
value = d[key]
except KeyError:
value = default
# or:
value = d.get(key, default)
Raising#
raise ValueError(f"bad input: {x!r}")
raise RuntimeError("unreachable") # belt-and-suspenders
# re-raise after logging
try:
work()
except OSError:
log.exception("work failed")
raise
# chain with `from` to keep the original
try:
parse(buf)
except ValueError as e:
raise ConfigError("bad config") from e
Exception tree#
The hierarchy you’ll see most.
flowchart TD
B[BaseException]
B --> SE[SystemExit]
B --> KI[KeyboardInterrupt]
B --> GE[GeneratorExit]
B --> E["Exception<br/><i>catch this, not BaseException</i>"]
E --> AE["ArithmeticError<br/>(ZeroDivisionError, OverflowError, ...)"]
E --> LE["LookupError<br/>(IndexError, KeyError)"]
E --> OS["OSError<br/>(FileNotFoundError, PermissionError,<br/>IsADirectoryError, ConnectionError, ...)"]
E --> VE["ValueError<br/>(UnicodeDecodeError, ...)"]
E --> TE[TypeError]
E --> RE["RuntimeError<br/>(RecursionError, NotImplementedError)"]
E --> SI["StopIteration / StopAsyncIteration"]
BaseException includes KeyboardInterrupt and SystemExit,
which you almost never want to suppress.
Custom exceptions#
class ConfigError(Exception):
"""Raised when config can't be loaded or validated."""
class HTTPError(Exception):
def __init__(self, status: int, body: str):
super().__init__(f"HTTP {status}: {body[:80]}")
self.status = status
self.body = body
Inherit from a meaningful base. Avoid Exception directly when a
more specific built-in fits (ValueError for bad input,
LookupError for missing keys, etc.).
ExceptionGroup#
Since Python 3.11 you can raise / catch multiple exceptions
together (e.g. parallel asyncio.gather):
try:
await asyncio.gather(a(), b(), c())
except* ValueError as eg:
for e in eg.exceptions:
handle(e)
Assertions#
assert is for invariants the developer expects to hold, not for
input validation; it’s stripped under python -O.
assert isinstance(x, int), "callers must pass ints"
Pitfalls#
Bare ``except:`` swallows
KeyboardInterrupt/SystemExit. Always name the exception class.``except Exception as e: pass`` silently hides bugs. Log it or re-raise.
Exception in ``finally:`` can mask the original; use
try / except / finallycarefully.Logging then re-raising, use
log.exception(...), which attaches the current traceback.
See also: man 1 python, https://docs.python.org/3/library/exceptions.html.