ExceptionGroup

Several errors at once, in one exception: except* lets each handler take the members of its type, and anything unhandled is re-raised.

InheritsBaseException›Exception›BaseExceptionGroup›ExceptionGroup
Base classPython 3.11+Live demo
ExceptionGroup(msgmsg — The group message, available as eg.message. Must be a str.type: str · required, excsexcs — Non-empty sequence of exception instances, stored as the tuple eg.exceptions. ExceptionGroup accepts only Exception subclasses; use BaseExceptionGroup for others.type: Sequence[Exception] · required)
Raised by
asyncio.TaskGroup, raise ExceptionGroup('msg', [e1, e2])
Message
msg (N sub-exceptions) — members are listed in the traceback
Quick fix
except* ValueError as eg: ... then eg.exceptions
Watch out
plain except ValueError does NOT match a group containing ValueErrors

Demo

Live evaluation
Validate every item, collect the failures, and raise them all at once instead of stopping at the first.
Try:
Inputs
valueslist[str]comma-separated
Code
def parse_all(values):
    nums, errors = [], []
    for v in values:
        try:
            nums.append(int(v))
        except ValueError as e:
            errors.append(e)
    if errors:
        raise ExceptionGroup('bad values', errors)
    return nums
try:
    r = parse_all(['1', '2', '3'])
except ExceptionGroup as eg:
    r = (str(eg), eg.exceptions)
r
Result
[1, 2, 3]

Every bad value is reported, not just the first — that is the point of a group. str(eg) only counts the members ('bad values (2 sub-exceptions)'); the details live in eg.exceptions. Uncaught, a group prints a traceback with one numbered section per member. In Raise, try n = 0: a group must contain at least one exception.

Constructor

NameTypeRequiredDescription
msgstryesThe group message, available as eg.message. Must be a str.
excsSequence[Exception]yesNon-empty sequence of exception instances, stored as the tuple eg.exceptions. ExceptionGroup accepts only Exception subclasses; use BaseExceptionGroup for others.

Attributes

AttributeTypeMeaning
messagestrThe msg argument. Read-only.
exceptionstupleThe member exceptions (may include nested groups). Read-only.
subgroup(condition)methodA new group with only the matching members (a type, a tuple of types, or since 3.13 any callable), or None if nothing matches.
split(condition)method(match, rest) — subgroup(condition) plus a group of the non-matching members; either side may be None.
derive(excs)methodSame message, new members. Override it in subclasses so subgroup()/split() build your class.

Common patterns

Report every validation failure
Collect errors in a list, raise them together at the end.
errors = []
for field, value in form.items():
    try:
        validate(field, value)
    except ValueError as e:
        e.add_note(f'field: {field}')
        errors.append(e)
if errors:
    raise ExceptionGroup('invalid form', errors)
Handle each type separately
Each except* clause runs at most once, with a group of just its matches. Unmatched members are re-raised.
try:
    run_all()
except* TimeoutError as eg:
    retry_later(len(eg.exceptions))
except* ConnectionError as eg:
    for e in eg.exceptions:
        log.warning('connection failed: %s', e)
Concurrent tasks with asyncio.TaskGroup
If tasks fail, TaskGroup cancels the rest and raises an ExceptionGroup of the failures.
async def main():
    try:
        async with asyncio.TaskGroup() as tg:
            for url in urls:
                tg.create_task(fetch(url))
    except* OSError as eg:
        print(f'{len(eg.exceptions)} downloads failed')

Examples

1. message and exceptions
eg = ExceptionGroup('two errors', [ValueError('a'), TypeError('b')]) (str(eg), eg.exceptions)
Returns
('two errors (2 sub-exceptions)', (ValueError('a'), TypeError('b')))
2. except* runs one handler per type
log = [] try: raise ExceptionGroup('many', [ValueError('a'), TypeError('b'), ValueError('c')]) except* ValueError as eg: log.append(('value', len(eg.exceptions))) except* TypeError as eg: log.append(('type', len(eg.exceptions))) log
Returns
[('value', 2), ('type', 1)]
3. Unhandled members are re-raised
try: raise ExceptionGroup('many', [ValueError('a'), KeyError('k')]) except* ValueError: pass
Returns
ExceptionGroup: many (1 sub-exception)
4. split() into match and rest
eg = ExceptionGroup('m', [ValueError('a'), TypeError('b')]) eg.split(ValueError)
Returns
(ExceptionGroup('m', [ValueError('a')]), ExceptionGroup('m', [TypeError('b')]))
5. subgroup() with a predicate (3.13+)
eg = ExceptionGroup('m', [ValueError('bad id'), ValueError('bad name')]) eg.subgroup(lambda e: 'id' in str(e))
Returns
ExceptionGroup('m', [ValueError('bad id')])
6. except* also catches a bare exception
try: raise ValueError('x') except* ValueError as eg: r = repr(eg) r
Returns
"ExceptionGroup('', (ValueError('x'),))"
7. BaseExceptionGroup picks the right class
[type(BaseExceptionGroup('m', [ValueError()])).__name__, type(BaseExceptionGroup('m', [KeyboardInterrupt()])).__name__]
Returns
['ExceptionGroup', 'BaseExceptionGroup']
8. asyncio.TaskGroup raises a group
import asyncio async def fail(x): raise ValueError(x) async def main(): async with asyncio.TaskGroup() as tg: tg.create_task(fail('a')) tg.create_task(fail('b')) try: asyncio.run(main()) except* ValueError as eg: r = (eg.message, sorted(str(e) for e in eg.exceptions)) r
Returns
('unhandled errors in a TaskGroup', ['a', 'b'])

Pitfalls

1. Plain except does not look inside the group
except ValueError matches only an exception whose type is ValueError. A group is an ExceptionGroup, so it goes straight past.
except ValueError
try:
    raise ExceptionGroup('batch', [ValueError('bad')])
except ValueError:
    r = 'handled'
r
ExceptionGroup: batch (1 sub-exception)
except* ValueError
try:
    raise ExceptionGroup('batch', [ValueError('bad')])
except* ValueError:
    r = 'handled'
r
'handled'
2. Mixing except and except*
A try statement uses either except or except* clauses, never both — it is a SyntaxError before anything runs.
except + except*
try:
    pass
except* ValueError:
    pass
except TypeError:
    pass
SyntaxError: cannot have both 'except' and 'except*' on the same 'try'
all except*
try:
    raise ExceptionGroup('m', [TypeError('t')])
except* ValueError:
    r = 'value'
except* TypeError:
    r = 'type'
r
'type'
3. ExceptionGroup cannot hold KeyboardInterrupt
ExceptionGroup only wraps Exception subclasses. For BaseExceptions use BaseExceptionGroup (or let it choose automatically).
ExceptionGroup
ExceptionGroup('stop', [KeyboardInterrupt()])
TypeError: Cannot nest BaseExceptions in an ExceptionGroup
BaseExceptionGroup
BaseExceptionGroup('stop', [KeyboardInterrupt()])
BaseExceptionGroup('stop', [KeyboardInterrupt()])
4. except* ExceptionGroup
except* already unpacks groups; asking it to match a group type is a TypeError at runtime. Use a plain except to catch the whole group.
except* ExceptionGroup
try:
    raise ExceptionGroup('m', [ValueError()])
except* ExceptionGroup:
    r = 'handled'
r
TypeError: catching ExceptionGroup with except* is not allowed. Use except instead.
except ExceptionGroup
try:
    raise ExceptionGroup('m', [ValueError()])
except ExceptionGroup as eg:
    r = len(eg.exceptions)
r
1

When to use

Use it
  • Concurrency: several tasks can fail at once (asyncio.TaskGroup, your own worker pools)
  • Validation that should report every problem, not just the first
  • Cleanup code where more than one step can fail and none should be lost
Reach for something else
  • Only one thing can fail → raise that exception directly
  • Code that must run on Python 3.10 or older → the exceptiongroup backport or a list attribute on your own exception
  • Just adding context to one error → add_note() or raise ... from e

Notes

CPython impl
Objects/exceptions.c — BaseExceptionGroup.__new__ returns an ExceptionGroup when every member is an Exception
PEP
PEP 654 — Exception Groups and except*
Nesting
Members may themselves be groups; subgroup()/split() preserve the nesting and drop empty sub-groups
except* limits
break, continue and return are not allowed inside an except* block

FAQ

except* (Python 3.11+) handles exception groups. except* ValueError as eg matches the ValueErrors inside a group; eg is a new ExceptionGroup containing just those. Every except* clause whose type matches runs once, and members no clause handled are re-raised in a group after the try statement.

History

3.11
ExceptionGroup and BaseExceptionGroup were added.
3.13
subgroup() and split() accept any callable (other than a type object) as the condition.