AI Skill Report Card

Handling Python Exceptions

A-85·Aug 13, 2026·Source: Web
14 / 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
13 / 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 finally or context managers, not duplicated code
Recommendation
Trim the hierarchy diagram slightly or move deep details to a reference section to tighten conciseness
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:

  • BaseException is the root of all exceptions. Almost never catch this directly.
  • Exception is what nearly all user code should subclass and catch. It excludes SystemExit, KeyboardInterrupt, and GeneratorExit, so catching Exception doesn't accidentally swallow program-termination signals.
  • ArithmeticError, LookupError, OSError are "umbrella" bases — catch them when you want to handle a family of related errors generically (e.g., except LookupError catches both IndexError and KeyError).
  • OSError subsumes what used to be separate IOError/WindowsError/socket.error — they're now aliases.
  • Every exception has .args, and supports __cause__ (explicit raise ... from) and __context__ (implicit chaining during handling).
  • BaseExceptionGroup/ExceptionGroup wrap multiple unrelated exceptions raised together (used with except* syntax).
18 / 20

Example 1: Choosing the right catch Input: Code that parses a JSON config file and looks up a key. Output:

Python
import 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:

Python
class 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:

Python
try: 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
  • 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 catches KeyboardInterrupt and SystemExit. Use except Exception: at minimum if you must catch broadly.
  • Use raise NewError(...) from original to preserve the causal chain instead of losing context.
  • Define custom exceptions as subclasses of Exception (not BaseException), and give a library a single root exception class so users can catch everything from it with one except.
  • Prefer context managers (with) over manual try/finally for resource cleanup.
  • Use exc.args or str(exc) for messages rather than parsing repr(exc).
  • Catching Exception (or worse, bare except:) to silence errors — hides bugs and swallows Ctrl-C.
  • Subclassing BaseException directly for custom errors — breaks user expectations that except Exception catches everything reasonable.
  • Re-raising with raise NewError(str(e)) instead of raise NewError(...) from e — destroys the traceback/context chain.
  • Confusing IndexError/KeyError (subclasses of LookupError) with AttributeError/TypeError, which are unrelated bases.
  • Forgetting that ModuleNotFoundError is a subclass of ImportError, so catching ImportError alone is usually sufficient.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
14/15
Workflow
13/15
Examples
18/20
Completeness
19/20
Format
14/15
Conciseness
13/15