AI Skill Report Card

Handling Python Exceptions

A-85·Aug 13, 2026·Source: Web
14 / 15
Python
# Raise the most specific built-in exception that fits def get_item(items, index): if not isinstance(index, int): raise TypeError(f"index must be int, got {type(index).__name__}") if index < 0 or index >= len(items): raise IndexError(f"index {index} out of range for length {len(items)}") return items[index] # Catch specific exceptions, not bare except try: value = get_item(my_list, user_input) except (TypeError, IndexError) as e: logger.error(f"Invalid access: {e}") raise
Recommendation
Add a 'bad vs good' example pair showing an anti-pattern (e.g., bare except) alongside the fix for clearer contrast
14 / 15

Progress:

  • Identify what error condition needs signaling
  • Check if a built-in exception fits exactly
  • If not, create a custom exception subclassing the closest built-in or Exception
  • Raise with a clear, actionable message
  • Catch at the appropriate level of specificity
  • Use finally/context managers for cleanup, not exception handling for control flow

Step 1: Choose the right built-in exception

Core hierarchy (all inherit from BaseException):

BaseException
 ├── SystemExit
 ├── KeyboardInterrupt
 ├── GeneratorExit
 └── Exception
      ├── StopIteration / StopAsyncIteration
      ├── ArithmeticError
      │    ├── ZeroDivisionError
      │    ├── OverflowError
      │    └── FloatingPointError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ImportError
      │    └── ModuleNotFoundError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── MemoryError
      ├── NameError
      │    └── UnboundLocalError
      ├── OSError
      │    ├── FileNotFoundError
      │    ├── FileExistsError
      │    ├── PermissionError
      │    ├── TimeoutError
      │    ├── InterruptedError
      │    ├── IsADirectoryError / NotADirectoryError
      │    └── ConnectionError (BrokenPipe/ConnectionReset/Refused/Aborted)
      ├── ReferenceError
      ├── RuntimeError
      │    ├── NotImplementedError
      │    ├── RecursionError
      │    └── PythonFinalizationError
      ├── SyntaxError
      │    └── IndentationError → TabError
      ├── SystemError
      ├── TypeError
      ├── ValueError
      │    └── UnicodeError (UnicodeDecodeError/EncodeError/TranslateError)
      └── Warning (DeprecationWarning, UserWarning, etc.)

Selection guide:

  • Wrong type passed → TypeError
  • Right type, invalid value → ValueError
  • Missing key/index → KeyError / IndexError
  • Missing attribute/name → AttributeError / NameError
  • Abstract method not implemented → NotImplementedError
  • Invalid state/logic error not covered elsewhere → RuntimeError
  • File/OS-level failure → appropriate OSError subclass

Step 2: Only catch Exception, never BaseException

BaseException includes SystemExit and KeyboardInterrupt — catching it breaks Ctrl-C and sys.exit(). Bare except: is equivalent to except BaseException: — avoid it.

Step 3: Use exception chaining for context

Python
try: parse_config(path) except FileNotFoundError as e: raise ConfigError(f"Cannot load config from {path}") from e # Suppress chaining when the original is irrelevant noise raise ConfigError("bad config") from None

Step 4: Custom exceptions — subclass meaningfully

Python
class AppError(Exception): """Base class for this application's exceptions.""" class ValidationError(AppError, ValueError): """Raised when input fails validation. Also catchable as ValueError.""" def __init__(self, field, message): self.field = field super().__init__(f"{field}: {message}")

Define one base exception per package/library so callers can catch broadly (except AppError) or narrowly.

Step 5: Use exception groups for multiple concurrent errors (3.11+)

Python
try: results = run_concurrent_tasks() except* ValueError as eg: for err in eg.exceptions: log_validation_error(err) except* ConnectionError as eg: for err in eg.exceptions: log_network_error(err)

Raise multiple related errors together with ExceptionGroup("summary", [err1, err2]).

Recommendation
Include an example of a completely custom exception hierarchy for a small app to show Step 4 in a fuller working context
16 / 20

Example 1: Input: Function receives a negative value where only non-negative is valid. Output:

Python
def set_quantity(n): if not isinstance(n, int): raise TypeError(f"quantity must be int, got {type(n).__name__}") if n < 0: raise ValueError(f"quantity must be non-negative, got {n}")

Example 2: Input: Catching a file read error and re-raising with context. Output:

Python
try: with open(path) as f: data = f.read() except FileNotFoundError as e: raise RuntimeError(f"Required file missing: {path}") from e

Example 3: Input: Abstract base class method that subclasses must implement. Output:

Python
class Shape: def area(self): raise NotImplementedError("Subclasses must implement area()")
Recommendation
Trim the full exception hierarchy diagram slightly or move to an appendix since Claude likely knows most of it, freeing space for more scenario-based examples
  • Raise the most specific exception available; only fall back to Exception/RuntimeError when nothing else fits.
  • Always include a descriptive message with relevant values (f"expected X, got {actual}").
  • Use except SpecificError: blocks in order from most to least specific.
  • Prefer try/except/else/finally fully: else for code that only runs on success, finally for cleanup.
  • Use context managers (with) instead of manual try/finally for resource cleanup.
  • Chain exceptions with from to preserve the original traceback/cause.
  • Check e.args, str(e), and exception attributes for structured error data rather than parsing message strings.
  • Use warnings.warn() with the Warning hierarchy for deprecations, not exceptions.
  • Bare except: — catches SystemExit/KeyboardInterrupt, hides bugs. Use except Exception: minimum.
  • Catching too broadly then swallowing errorsexcept Exception: pass hides real bugs; at least log them.
  • Using exceptions for normal control flow — e.g., using KeyError catch instead of dict.get() for expected-missing keys.
  • Re-raising with raise e — destroys the original traceback; use bare raise inside an except block to preserve it.
  • Mutable default state in custom exception __init__ — avoid mutable defaults as args.
  • Forgetting ModuleNotFoundError is a subclass of ImportError — catching ImportError also catches missing-module errors; be specific if you need to distinguish.
  • Overusing custom exceptions — don't create a new exception class when a built-in with a good message suffices.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
14/15
Workflow
14/15
Examples
16/20
Completeness
19/20
Format
14/15
Conciseness
13/15