AI Skill Report Card
Handling Python Exceptions
Quick Start14 / 15
Python# Catch specific exceptions, not bare Exception try: value = int(user_input) except ValueError as e: print(f"Invalid input: {e}") except (TypeError, KeyError) as e: print(f"Unexpected type/key issue: {e}") # Raise with context try: process() except OSError as e: raise RuntimeError("processing failed") from e # Custom exception class ConfigError(Exception): """Raised when configuration is invalid."""
Recommendation▾
Add an example showing a bad/anti-pattern output alongside a good one for contrast
Workflow13 / 15
Progress:
- Identify what can go wrong and which built-in exception naturally fits
- Choose the narrowest exception class(es) to catch
- Decide whether to handle, re-raise, or wrap the exception
- Add custom exception classes only when built-ins don't convey domain meaning
- Use
raise ... from ...when re-raising in an except block - Clean up resources with
finallyor context managers, not duplicated code
Recommendation▾
Trim the hierarchy diagram slightly or move deep details to a reference section to tighten conciseness
Exception Hierarchy (base classes)
BaseException
├── BaseExceptionGroup
├── GeneratorExit
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── ExceptionGroup(BaseExceptionGroup, Exception)
├── ArithmeticError (ZeroDivisionError, OverflowError, FloatingPointError)
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ImportError (ModuleNotFoundError)
├── LookupError (IndexError, KeyError)
├── MemoryError
├── NameError (UnboundLocalError)
├── OSError (FileNotFoundError, PermissionError, TimeoutError, ...)
├── ReferenceError
├── RuntimeError (NotImplementedError, RecursionError)
├── StopIteration / StopAsyncIteration
├── SyntaxError (IndentationError → TabError)
├── SystemError
├── TypeError
├── ValueError (UnicodeError → UnicodeDecodeError/EncodeError/TranslateError)
└── Warning (DeprecationWarning, UserWarning, ...)
Key base-class facts:
BaseExceptionis the root of all exceptions. Almost never catch this directly.Exceptionis what nearly all user code should subclass and catch. It excludesSystemExit,KeyboardInterrupt, andGeneratorExit, so catchingExceptiondoesn't accidentally swallow program-termination signals.ArithmeticError,LookupError,OSErrorare "umbrella" bases — catch them when you want to handle a family of related errors generically (e.g.,except LookupErrorcatches bothIndexErrorandKeyError).OSErrorsubsumes what used to be separateIOError/WindowsError/socket.error— they're now aliases.- Every exception has
.args, and supports__cause__(explicitraise ... from) and__context__(implicit chaining during handling). BaseExceptionGroup/ExceptionGroupwrap multiple unrelated exceptions raised together (used withexcept*syntax).
Examples18 / 20
Example 1: Choosing the right catch Input: Code that parses a JSON config file and looks up a key. Output:
Pythonimport json try: with open(path) as f: config = json.load(f) value = config["timeout"] except FileNotFoundError: raise ConfigError(f"config file not found: {path}") except json.JSONDecodeError as e: raise ConfigError(f"invalid JSON: {e}") from e except KeyError: raise ConfigError("missing required key 'timeout'")
Example 2: Custom exception hierarchy Input: A library needs its own errors that users can catch broadly or specifically. Output:
Pythonclass LibraryError(Exception): """Base class for all errors raised by this library.""" class ValidationError(LibraryError): """Raised when input validation fails.""" class ConnectionFailedError(LibraryError): """Raised when a network connection cannot be established.""" # Users can do: try: do_thing() except LibraryError: # catches both subclasses ...
Example 3: except* for exception groups
Input: A task runner collects failures from multiple concurrent subtasks.
Output:
Pythontry: async with asyncio.TaskGroup() as tg: tg.create_task(fetch(a)) tg.create_task(fetch(b)) except* ValueError as eg: for e in eg.exceptions: log.error("bad value: %s", e) except* TimeoutError as eg: for e in eg.exceptions: log.error("timed out: %s", e)
Recommendation▾
Include a brief note on logging exceptions (e.g., logger.exception) as a common real-world pattern
Best Practices
- Catch the most specific exception type that makes sense; fall back to umbrella bases (
LookupError,OSError) only when handling truly applies to the whole family. - Never bare
except:— it also catchesKeyboardInterruptandSystemExit. Useexcept Exception:at minimum if you must catch broadly. - Use
raise NewError(...) from originalto preserve the causal chain instead of losing context. - Define custom exceptions as subclasses of
Exception(notBaseException), and give a library a single root exception class so users can catch everything from it with oneexcept. - Prefer context managers (
with) over manualtry/finallyfor resource cleanup. - Use
exc.argsorstr(exc)for messages rather than parsingrepr(exc).
Common Pitfalls
- Catching
Exception(or worse, bareexcept:) to silence errors — hides bugs and swallows Ctrl-C. - Subclassing
BaseExceptiondirectly for custom errors — breaks user expectations thatexcept Exceptioncatches everything reasonable. - Re-raising with
raise NewError(str(e))instead ofraise NewError(...) from e— destroys the traceback/context chain. - Confusing
IndexError/KeyError(subclasses ofLookupError) withAttributeError/TypeError, which are unrelated bases. - Forgetting that
ModuleNotFoundErroris a subclass ofImportError, so catchingImportErroralone is usually sufficient.