C.W.K.
Stream
Lesson 06 of 06 · published

ExceptionGroup and except* — Multiple Exceptions at Once

~18 min · exceptiongroup, except-star, 3.11, asyncio

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The new shape (3.11+) — multiple exceptions in flight

Until Python 3.11, raising and catching exceptions was strictly serial: one exception at a time. But concurrent code — asyncio with TaskGroup, parallel I/O, batch operations — sometimes has multiple things fail simultaneously. ExceptionGroup is the language's answer: a single exception that wraps a list of others, and except* is the new syntax for handling specific types within the group.

ExceptionGroup — wraps a list of exceptions

You raise ExceptionGroup(message, list_of_excs). The group itself is an exception; the constituent exceptions are accessible via .exceptions. Tracebacks show the whole tree.

except* — catch a type, leave the rest

except* SomeError as eg: catches all SomeErrors within the group, regardless of nesting depth. The bound eg is a new ExceptionGroup containing only the matched ones. Other exceptions in the original group continue to propagate; multiple except* blocks can each catch their own type.

asyncio.TaskGroup — where you'll meet this first

The most common producer of ExceptionGroup in real code is asyncio.TaskGroup (3.11+). If multiple tasks fail concurrently, the group bundles all their exceptions into one ExceptionGroup. Handling becomes try: async with TaskGroup() as tg: ...; except* OneType as eg: ...; except* OtherType as eg: ....

Principle: Most application code doesn't need to construct ExceptionGroups directly. You'll mainly encounter them when using TaskGroup or other concurrent primitives. Knowing the except* syntax means you can handle multi-failure cases cleanly when they arise.

Code

Raising and catching an ExceptionGroup·python
# Python 3.11+
try:
    raise ExceptionGroup(
        "multiple problems",
        [
            ValueError("bad value"),
            KeyError("missing"),
            TypeError("wrong shape"),
        ],
    )
except* ValueError as eg:
    print("caught value errors:", [str(e) for e in eg.exceptions])
except* KeyError as eg:
    print("caught key errors:", [str(e) for e in eg.exceptions])
# TypeError in the original group propagates further up — neither except* matched it
ExceptionGroup with asyncio.TaskGroup (3.11+)·python
# import asyncio
#
# async def fail_a():
#     raise ValueError("a failed")
#
# async def fail_b():
#     raise KeyError("b failed")
#
# async def main():
#     try:
#         async with asyncio.TaskGroup() as tg:
#             tg.create_task(fail_a())
#             tg.create_task(fail_b())
#     except* ValueError as eg:
#         print("value errors:", [str(e) for e in eg.exceptions])
#     except* KeyError as eg:
#         print("key errors:", [str(e) for e in eg.exceptions])
#
# asyncio.run(main())
# Both tasks ran concurrently; both failures collected into the group; both handled.
Nested ExceptionGroups — except* sees them flat·python
try:
    raise ExceptionGroup(
        "outer",
        [
            ValueError("v1"),
            ExceptionGroup("inner", [ValueError("v2"), KeyError("k1")]),
        ],
    )
except* ValueError as eg:
    print("all ValueErrors:", [str(e) for e in eg.exceptions])
    # Both v1 (top level) and v2 (nested) — except* recurses
except* KeyError as eg:
    print("all KeyErrors:", [str(e) for e in eg.exceptions])
Without except* — single-pick handling (legacy style)·python
# If you don't have 3.11 or want the old-style handling:
try:
    raise ExceptionGroup("trouble", [ValueError("x"), KeyError("y")])
except ExceptionGroup as eg:
    for e in eg.exceptions:
        print("individual:", type(e).__name__, e)
# This works but doesn't handle nesting — for that you need except*

External links

Exercise

Construct an ExceptionGroup with three constituent exceptions: a ValueError, a KeyError, and another nested ExceptionGroup containing a TypeError and a second ValueError. Then write a try/except* block that handles all ValueErrors in one branch and all KeyErrors in another. Print the exception messages from each branch. Verify that nested ValueErrors are correctly grouped together by except*.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.