AI Skill Report Card
Handling Python Exceptions
Quick Start14 / 15
Pythondef 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
Workflow14 / 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
fromwhen 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
| Situation | Exception |
|---|---|
| Wrong type passed | TypeError |
| Right type, invalid value | ValueError |
| Numeric value out of representable range | OverflowError |
| Sequence/mapping index or key doesn't exist | IndexError / KeyError (both subclass LookupError) |
| Attribute doesn't exist | AttributeError |
| Name not defined/bound | NameError (UnboundLocalError for locals) |
| Division/modulo by zero | ZeroDivisionError (subclass of ArithmeticError) |
| Object used in wrong state (e.g. closed file, exhausted generator) | RuntimeError or StopIteration/StopAsyncIteration as applicable |
| Method not implemented in subclass | NotImplementedError |
| Recursion too deep | RecursionError |
| File/resource missing or inaccessible | OSError and subclasses: FileNotFoundError, FileExistsError, PermissionError, IsADirectoryError, NotADirectoryError, InterruptedError, TimeoutError, ConnectionError (+ BrokenPipeError, ConnectionResetError, ConnectionAbortedError, ConnectionRefusedError) |
| Import fails | ImportError / ModuleNotFoundError |
| String/bytes can't be parsed as expected format | SyntaxError, UnicodeError (UnicodeDecodeError/UnicodeEncodeError/UnicodeTranslateError) |
| Assertion failed | AssertionError |
| Iterator exhausted | StopIteration |
| Multiple unrelated errors from concurrent/grouped operations | ExceptionGroup / 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 originalwhen converting one exception into another to preserve the causal chain; usefrom Noneto 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
KeyboardInterruptorSystemExit(they subclassBaseException, notException) — don't use bareexcept:for that reason. - Use
except* SomeError:(3.11+) when handlingExceptionGroups 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
Examples17 / 20
Example 1: Input: Function receives a negative value for a "count" parameter that must be non-negative. Output:
Pythonif 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:
Pythontry: 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:
Pythontry: 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:
Pythonclass 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
Best Practices
- Prefer
LookupError's subclasses (KeyError,IndexError) over generic exceptions for missing data access. - Use
OSErrorsubclasses instead of checkingerrnomanually — Python maps errno values automatically since 3.3. - Reserve
RuntimeErrorfor 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
ExceptionGrouprather than picking just one to propagate.
Common Pitfalls
- Using
Exceptionor bareexcept:to catch everything — hides bugs and can swallowKeyboardInterrupt/SystemExitif written as bareexcept:. - Raising
ValueErrorfor wrong type — that'sTypeError's job. - Re-raising with
raise NewError(str(e))instead ofraise NewError(...) from e, losing the traceback chain. - Defining custom exceptions as direct
Exceptionsubclasses when a built-in subclass would let existing handlers catch them. - Using
assertfor input validation in production code — asserts are stripped under-Oand are for invariants, not user input checks. - Catching
StopIterationbroadly inside a generator body (raisesRuntimeErrorper PEP 479) instead of letting it propagate naturally or handling it explicitly.