AI Skill Report Card

Handling Exception Groups

A-87·Aug 14, 2026·Source: Web

Quick Start

Python
# Raising multiple exceptions at once try: raise ExceptionGroup("multiple failures", [ ValueError("bad value"), TypeError("bad type"), ]) except* ValueError as eg: print(f"Caught ValueErrors: {eg.exceptions}") except* TypeError as eg: print(f"Caught TypeErrors: {eg.exceptions}")

except* matches by exception type against each leaf exception in the group, splits the group into "matched" and "unmatched" subgroups, and re-raises any unmatched portion after all clauses run.

13 / 15

Progress:

  • Step 1: Identify whether errors arise from concurrent/parallel operations (asyncio tasks, thread pools, batch validation) — these are prime candidates for ExceptionGroup
  • Step 2: Decide grouping strategy — one flat group, or nested groups preserving sub-task boundaries
  • Step 3: Construct the group with ExceptionGroup(msg, [exc1, exc2, ...]) or BaseExceptionGroup if any exception is a BaseException (e.g., KeyboardInterrupt, SystemExit)
  • Step 4: Handle with except* clauses ordered from most specific to most general type
  • Step 5: Verify unmatched exceptions propagate correctly (don't swallow silently)
  • Step 6: Test with .split() / .subgroup() if you need programmatic filtering instead of except*
Recommendation
Add a brief example showing what happens when except* leaves unmatched exceptions propagating (concrete traceback output) to reinforce the pitfall.
18 / 20

Example 1: Basic construction and type selection

Input:

Python
excs = [ValueError("v1"), ValueError("v2")] KeyboardInterrupt # one of the exceptions is a BaseException, not Exception

Output:

Python
# Because KeyboardInterrupt is not an Exception, must use BaseExceptionGroup eg = BaseExceptionGroup("mixed", [ValueError("v1"), KeyboardInterrupt()]) # ExceptionGroup() would raise TypeError here since it requires all Exception subclasses

Example 2: Nested groups from concurrent tasks

Input:

Python
import asyncio async def worker(n): if n % 2 == 0: raise ValueError(f"even fail {n}") raise TypeError(f"odd fail {n}") async def main(): async with asyncio.TaskGroup() as tg: for i in range(4): tg.create_task(worker(i))

Output:

Python
try: asyncio.run(main()) except* ValueError as eg: print("ValueErrors:", [str(e) for e in eg.exceptions]) except* TypeError as eg: print("TypeErrors:", [str(e) for e in eg.exceptions]) # TaskGroup automatically collects all task failures into one ExceptionGroup

Example 3: Programmatic filtering without except*

Input:

Python
eg = ExceptionGroup("errors", [ValueError("a"), TypeError("b"), ValueError("c")])

Output:

Python
value_errors, rest = eg.split(ValueError) # value_errors: ExceptionGroup("errors", [ValueError("a"), ValueError("c")]) # rest: ExceptionGroup("errors", [TypeError("b")])
Recommendation
Include a short note on Python version requirement (3.11+) since except* syntax is version-gated, which is a critical practical constraint.
  • Use except* (not except) whenever you're catching from a block that may raise ExceptionGroup/BaseExceptionGroup — mixing them raises SyntaxError.
  • Choose BaseExceptionGroup only when necessary (contains a BaseException); prefer ExceptionGroup for normal error handling since it's a subclass and satisfies both.
  • Order except* clauses from specific to general — like regular except, but each clause can still match if the group contains a mix of types.
  • Preserve nesting from source tasks/operations rather than flattening, so .exceptions retains context (e.g., which task failed).
  • Use traceback.print_exception(eg) for readable nested tracebacks — the standard traceback module has explicit support for exception groups.
  • Attach __notes__ via add_note() for extra context on individual exceptions before grouping, rather than stuffing details into the group message.
  • Don't mix bare except and except* in the same try statement — it's a SyntaxError.
  • Don't assume except* clause runs once per matching exception type only — it collects all matching leaf exceptions from the (possibly nested) group into one subgroup and runs the handler once.
  • Don't forget that unhandled/unmatched exceptions inside the group still propagate — an except* block that doesn't cover all types will re-raise a new group with the leftovers.
  • Don't call ExceptionGroup(msg, []) — an empty exceptions list raises ValueError; a group must always contain at least one exception.
  • Don't use isinstance(e, ExceptionGroup) checks inside a plain except and manually iterate .exceptions when except* already does this correctly, including nested groups.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
14/15
Workflow
13/15
Examples
18/20
Completeness
18/20
Format
14/15
Conciseness
14/15