AI Skill Report Card
Handling Python Exceptions
Quick Start14 / 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
Workflow14 / 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
OSErrorsubclass
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
Pythontry: 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
Pythonclass 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+)
Pythontry: 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
Examples16 / 20
Example 1: Input: Function receives a negative value where only non-negative is valid. Output:
Pythondef 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:
Pythontry: 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:
Pythonclass 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
Best Practices
- Raise the most specific exception available; only fall back to
Exception/RuntimeErrorwhen 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/finallyfully:elsefor code that only runs on success,finallyfor cleanup. - Use context managers (
with) instead of manualtry/finallyfor resource cleanup. - Chain exceptions with
fromto 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 theWarninghierarchy for deprecations, not exceptions.
Common Pitfalls
- Bare
except:— catchesSystemExit/KeyboardInterrupt, hides bugs. Useexcept Exception:minimum. - Catching too broadly then swallowing errors —
except Exception: passhides real bugs; at least log them. - Using exceptions for normal control flow — e.g., using
KeyErrorcatch instead ofdict.get()for expected-missing keys. - Re-raising with
raise e— destroys the original traceback; use bareraiseinside anexceptblock to preserve it. - Mutable default state in custom exception
__init__— avoid mutable defaults as args. - Forgetting
ModuleNotFoundErroris a subclass ofImportError— catchingImportErroralso 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.