Navigating Python Exception Hierarchy
Python# Catch the most specific applicable exception, not a broad parent try: value = int(user_input) except ValueError: # specific: str couldn't convert to int print("Invalid number") # Never catch BaseException or bare except unless re-raising try: risky_operation() except Exception: # catches app errors, NOT SystemExit/KeyboardInterrupt log_and_continue()
- Identify the failure mode — what actually goes wrong (bad type, bad value, missing key, missing attribute, IO failure)?
- Locate it in the hierarchy — pick the narrowest built-in exception that matches semantically.
- Decide catch scope — catch only what you can meaningfully handle; let everything else propagate.
- Consider custom exceptions — if this is domain logic, subclass
Exception(neverBaseExceptiondirectly). - Verify ordering — in multi-except blocks, list more specific exceptions before more general ones.
BaseException
├── BaseExceptionGroup
├── GeneratorExit
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ExceptionGroup [BaseExceptionGroup]
├── ImportError
│ └── ModuleNotFoundError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── MemoryError
├── NameError
│ └── UnboundLocalError
├── OSError
│ ├── BlockingIOError
│ ├── ChildProcessError
│ ├── ConnectionError
│ │ ├── BrokenPipeError
│ │ ├── ConnectionAbortedError
│ │ ├── ConnectionRefusedError
│ │ └── ConnectionResetError
│ ├── FileExistsError
│ ├── FileNotFoundError
│ ├── InterruptedError
│ ├── IsADirectoryError
│ ├── NotADirectoryError
│ ├── PermissionError
│ ├── ProcessLookupError
│ └── TimeoutError
├── ReferenceError
├── RuntimeError
│ ├── NotImplementedError
│ ├── PythonFinalizationError
│ └── RecursionError
├── StopAsyncIteration
├── StopIteration
├── SyntaxError
│ └── IndentationError
│ └── TabError
├── SystemError
├── TypeError
├── ValueError
│ └── UnicodeError
│ ├── UnicodeDecodeError
│ ├── UnicodeEncodeError
│ └── UnicodeTranslateError
└── Warning
├── BytesWarning
├── DeprecationWarning
├── EncodingWarning
├── FutureWarning
├── ImportWarning
├── PendingDeprecationWarning
├── ResourceWarning
├── RuntimeWarning
├── SyntaxWarning
├── UnicodeWarning
└── UserWarning
Key structural facts:
Exceptionis the base for essentially all program-level errors;except Exceptionwill not catchSystemExit,KeyboardInterrupt, orGeneratorExit, since these inherit directly fromBaseException.OSErrorunifies what used to beIOError,EnvironmentError,WindowsError, and socket errors — these are now aliases.ImportError→ModuleNotFoundErroris raised when a module isn't found at all (subclass); plainImportErrorcovers other import failures (e.g., name not found in module).LookupErroris the parent ofIndexErrorandKeyError— catchLookupErrorwhen handling "container access failed" generically.NameError→UnboundLocalErrorspecifically means a local variable was referenced before assignment.ArithmeticErrorgroupsZeroDivisionError,OverflowError,FloatingPointError.ExceptionGroup/BaseExceptionGroupwrap multiple exceptions raised together (used withexcept*syntax).Warningis a subclass ofExceptionbut conceptually separate — warnings are typically shown, not raised, via thewarningsmodule.
Example 1:
Input: Code does d["missing_key"] and needs to handle missing keys.
Output: except KeyError: — not the broader LookupError unless also handling list index errors in the same block.
Example 2:
Input: Need one handler for both list[idx] out-of-range and dict[key] missing.
Output: except LookupError: since it's the common parent of IndexError and KeyError.
Example 3:
Input: Writing a top-level handler that should log unexpected errors but let the user Ctrl-C out and let sys.exit() work.
Output: except Exception: (not except BaseException:), so KeyboardInterrupt and SystemExit propagate normally.
Example 4: Input: Custom domain error for "insufficient funds" in a banking module. Output:
Pythonclass InsufficientFundsError(Exception): """Raised when an account lacks funds for a transaction."""
Subclass Exception, not BaseException or RuntimeError, unless there's a stronger semantic fit.
- Catch the narrowest exception that lets you handle the error meaningfully.
- Order
exceptclauses from most specific to least specific — Python checks them top to bottom. - Use
except (TypeError, ValueError):for tuples of unrelated but co-handled exceptions instead of catching a shared broad ancestor. - When re-raising with added context, use
raise NewError(...) from original_excto preserve the chain. - Define custom exceptions per module/package, subclassing
Exception, and build a small hierarchy of your own if the domain has multiple related error types. - Use
except*andExceptionGroupwhen concurrent tasks (e.g.,asyncio.TaskGroup) may raise multiple independent exceptions.
- Using bare
except:— catchesBaseException, swallowingKeyboardInterrupt/SystemExit, making programs unkillable via Ctrl-C in bad cases. - Catching
Exceptionbroadly "just in case" and silently passing — hides real bugs. - Confusing
ImportErrorandModuleNotFoundError— only the latter is guaranteed when a module doesn't exist; catchImportErrorif also handling partial/attribute import failures. - Assuming
IOError/EnvironmentErrorstill exist as distinct types — they're aliases ofOSErrornow. - Placing a general exception (e.g.,
Exception) before a specific one (e.g.,ValueError) in the sametryblock — the specific branch becomes unreachable. - Treating
Warningsubclasses as things toexceptin normal control flow — they're meant for thewarningsfilter system, not typical error handling.