AI Skill Report Card

Handling Python Exceptions

A90·Aug 13, 2026·Source: Web
14 / 15
Python
def get_item(container, key, index): if not isinstance(key, str): raise TypeError(f"key must be str, got {type(key).__name__}") if index < 0 or index >= len(container): raise IndexError(f"index {index} out of range") try: return container[key][index] except KeyError as e: raise LookupError(f"key {key!r} not found") from e

Pick the exception based on the nature of the error, not convenience. Always raise the most specific applicable built-in before inventing a custom one.

Recommendation
Add a concrete example showing a custom exception hierarchy being defined and used end-to-end
14 / 15

Progress:

  • Identify the failure category (bad value, bad type, missing resource, bad state, environment/system failure)
  • Match to the most specific built-in exception
  • Add a clear, actionable message with relevant values
  • Chain exceptions with from when translating one error into another
  • Only subclass a built-in when the built-ins genuinely don't fit
  • Catch narrowly; never bare except: unless re-raising or at a top-level boundary

Step 1: Classify the failure

SituationException
Wrong type passedTypeError
Right type, invalid valueValueError
Numeric value out of representable rangeOverflowError
Sequence/mapping index or key doesn't existIndexError / KeyError (both subclass LookupError)
Attribute doesn't existAttributeError
Name not defined/boundNameError (UnboundLocalError for locals)
Division/modulo by zeroZeroDivisionError (subclass of ArithmeticError)
Object used in wrong state (e.g. closed file, exhausted generator)RuntimeError or StopIteration/StopAsyncIteration as applicable
Method not implemented in subclassNotImplementedError
Recursion too deepRecursionError
File/resource missing or inaccessibleOSError and subclasses: FileNotFoundError, FileExistsError, PermissionError, IsADirectoryError, NotADirectoryError, InterruptedError, TimeoutError, ConnectionError (+ BrokenPipeError, ConnectionResetError, ConnectionAbortedError, ConnectionRefusedError)
Import failsImportError / ModuleNotFoundError
String/bytes can't be parsed as expected formatSyntaxError, UnicodeError (UnicodeDecodeError/UnicodeEncodeError/UnicodeTranslateError)
Assertion failedAssertionError
Iterator exhaustedStopIteration
Multiple unrelated errors from concurrent/grouped operationsExceptionGroup / BaseExceptionGroup

Step 2: Write the raise site

  • Include the offending value(s) in the message: f"expected positive int, got {n}".
  • Never raise a bare string or non-exception object.
  • Use raise X(...) from original when converting one exception into another to preserve the causal chain; use from None to deliberately suppress chaining noise.

Step 3: Catch precisely

  • Catch the narrowest exception(s) that a caller can meaningfully recover from.
  • Catch tuples explicitly rather than broad supertypes: except (KeyError, IndexError):
  • Never swallow KeyboardInterrupt or SystemExit (they subclass BaseException, not Exception) — don't use bare except: for that reason.
  • Use except* SomeError: (3.11+) when handling ExceptionGroups from concurrent tasks.

Step 4: Custom exceptions only when needed

Subclass the closest matching built-in (e.g. class ConfigError(ValueError):) rather than Exception directly, so existing except ValueError handlers still work.

Recommendation
Include a 'bad output' example (e.g., raising Exception generically) contrasted with the correct fix to reinforce pitfalls
17 / 20

Example 1: Input: Function receives a negative value for a "count" parameter that must be non-negative. Output:

Python
if count < 0: raise ValueError(f"count must be non-negative, got {count}")

Example 2: Input: A dict lookup fails inside a function that's translating a public-facing API error. Output:

Python
try: config = registry[name] except KeyError as e: raise LookupError(f"no config registered under {name!r}") from e

Example 3: Input: Opening a file that may not exist. Output:

Python
try: with open(path) as f: data = f.read() except FileNotFoundError: data = default_data except PermissionError as e: raise RuntimeError(f"cannot read {path}: insufficient permissions") from e

Example 4: Input: An abstract base class method that subclasses must override. Output:

Python
class Shape: def area(self): raise NotImplementedError("subclasses must implement area()")
Recommendation
Consider trimming the classification table slightly or reformatting for scannability given its length relative to the rest of the skill
  • Prefer LookupError's subclasses (KeyError, IndexError) over generic exceptions for missing data access.
  • Use OSError subclasses instead of checking errno manually — Python maps errno values automatically since 3.3.
  • Reserve RuntimeError for genuinely unclassifiable state errors; don't use it as a catch-all default.
  • Use exception chaining (raise ... from ...) whenever translating exceptions across abstraction layers — it preserves debuggability.
  • When catching to log-and-reraise, use bare raise (no argument) to preserve the original traceback.
  • Group related concurrent failures with ExceptionGroup rather than picking just one to propagate.
  • Using Exception or bare except: to catch everything — hides bugs and can swallow KeyboardInterrupt/SystemExit if written as bare except:.
  • Raising ValueError for wrong type — that's TypeError's job.
  • Re-raising with raise NewError(str(e)) instead of raise NewError(...) from e, losing the traceback chain.
  • Defining custom exceptions as direct Exception subclasses when a built-in subclass would let existing handlers catch them.
  • Using assert for input validation in production code — asserts are stripped under -O and are for invariants, not user input checks.
  • Catching StopIteration broadly inside a generator body (raises RuntimeError per PEP 479) instead of letting it propagate naturally or handling it explicitly.
0
Grade AAI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
14/15
Workflow
14/15
Examples
17/20
Completeness
19/20
Format
15/15
Conciseness
14/15