AI Skill Report Card

Navigating Python Exception Hierarchy

A88·Aug 14, 2026·Source: Web
13 / 15
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()
Recommendation
Add a bad-example contrast (e.g., showing a broad except catching too much and causing a bug) to strengthen the examples section
13 / 15
  1. Identify the failure mode — what actually goes wrong (bad type, bad value, missing key, missing attribute, IO failure)?
  2. Locate it in the hierarchy — pick the narrowest built-in exception that matches semantically.
  3. Decide catch scope — catch only what you can meaningfully handle; let everything else propagate.
  4. Consider custom exceptions — if this is domain logic, subclass Exception (never BaseException directly).
  5. Verify ordering — in multi-except blocks, list more specific exceptions before more general ones.
Recommendation
Include a brief example of custom exception hierarchy with multiple related error types, not just a single class
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:

  • Exception is the base for essentially all program-level errors; except Exception will not catch SystemExit, KeyboardInterrupt, or GeneratorExit, since these inherit directly from BaseException.
  • OSError unifies what used to be IOError, EnvironmentError, WindowsError, and socket errors — these are now aliases.
  • ImportErrorModuleNotFoundError is raised when a module isn't found at all (subclass); plain ImportError covers other import failures (e.g., name not found in module).
  • LookupError is the parent of IndexError and KeyError — catch LookupError when handling "container access failed" generically.
  • NameErrorUnboundLocalError specifically means a local variable was referenced before assignment.
  • ArithmeticError groups ZeroDivisionError, OverflowError, FloatingPointError.
  • ExceptionGroup/BaseExceptionGroup wrap multiple exceptions raised together (used with except* syntax).
  • Warning is a subclass of Exception but conceptually separate — warnings are typically shown, not raised, via the warnings module.
17 / 20

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:

Python
class InsufficientFundsError(Exception): """Raised when an account lacks funds for a transaction."""

Subclass Exception, not BaseException or RuntimeError, unless there's a stronger semantic fit.

Recommendation
Add a short section on exception chaining (raise...from) with a concrete before/after code example
  • Catch the narrowest exception that lets you handle the error meaningfully.
  • Order except clauses 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_exc to 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* and ExceptionGroup when concurrent tasks (e.g., asyncio.TaskGroup) may raise multiple independent exceptions.
  • Using bare except: — catches BaseException, swallowing KeyboardInterrupt/SystemExit, making programs unkillable via Ctrl-C in bad cases.
  • Catching Exception broadly "just in case" and silently passing — hides real bugs.
  • Confusing ImportError and ModuleNotFoundError — only the latter is guaranteed when a module doesn't exist; catch ImportError if also handling partial/attribute import failures.
  • Assuming IOError/EnvironmentError still exist as distinct types — they're aliases of OSError now.
  • Placing a general exception (e.g., Exception) before a specific one (e.g., ValueError) in the same try block — the specific branch becomes unreachable.
  • Treating Warning subclasses as things to except in normal control flow — they're meant for the warnings filter system, not typical error handling.
0
Grade AAI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
13/15
Workflow
13/15
Examples
17/20
Completeness
18/20
Format
15/15
Conciseness
14/15