Typorithm · medium

Exception groups and matching subsets

pythonlibrariespackagingindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Exception groups and matching subsets

0%

Concept and contract

Handle known failures in a flat batch while allowing unexpected exception types to propagate.

Several independent failures may need to travel together without losing their individual types. ExceptionGroup represents such a collection, and except* selects the matching subset rather than treating the entire group as one ordinary ValueError or TypeError.

The owned example creates a flat group of two known error categories and counts each matching subset. Its count logic intentionally assumes a flat group: len(group.exceptions) counts immediate children, not recursively flattened leaves of arbitrary nested groups. Stating that boundary prevents a small teaching example from making an incorrect general claim about every grouped exception structure.

Mechanism and worked example

Input labels create ValueError or TypeError instances in order.

Input labels create ValueError or TypeError instances in order. A nonempty batch raises the group, and the two except* handlers receive their corresponding matching subsets. For value, type and value, the report records two value errors and one type error.

An empty input raises nothing and retains both zero counts. No handler needs to inspect message text to guess the category. The returned dictionary is assembled after the handlers finish; the code does not try to return directly from inside an except* block, which has different control-flow restrictions from an ordinary loop body.

Complete worked example

python

def inspect_example(kinds):
    errors = [ValueError('bad value') if kind == 'value' else TypeError('bad type')
              for kind in kinds]
    report = {'value': 0, 'type': 0}
    try:
        if errors:
            raise ExceptionGroup('owned batch', errors)
    except* ValueError as group:
        report['value'] = len(group.exceptions)
    except* TypeError as group:
        report['type'] = len(group.exceptions)
    return report
Reasoning
Input labels create ValueError or TypeError instances in order. A nonempty batch raises the group, and the two except* handlers receive their corresponding matching subsets. For value, type and value, the report records two value errors and one type error.
Download the reusable Python and packaging computer lab ↗

Focused decision: use the complete example for context

python

except* ValueError as group:
    report["value"] = len(group.exceptions)
Decision
Count matching error leaves for this flat group and add the complete total without suppressing unexpected exception types.
Download the reusable Python and packaging computer lab ↗

Counterexamples and common mistakes

The counterexample changes the TypeError handler to KeyError.

The counterexample changes the TypeError handler to KeyError. ValueError entries are still handled, but the TypeError remainder propagates as an ExceptionGroup. The verifier expects that concrete unhandled-group failure rather than accepting a partial-success report.

A broad catch of the entire group could hide those missing handlers and misleadingly report completion. Message matching is also weaker than matching the intended exception types. Decide which categories are recoverable at the current boundary, handle only those deliberately and let unknown categories remain visible to a caller that can make the next decision.

Controlled bug: the executable contract rejects this version

python

def inspect_example(kinds):
    errors = [ValueError('bad value') if kind == 'value' else TypeError('bad type')
              for kind in kinds]
    report = {'value': 0, 'type': 0}
    try:
        if errors:
            raise ExceptionGroup('owned batch', errors)
    except* ValueError as group:
        report['value'] = len(group.exceptions)
    except* KeyError as group:
        report['type'] = len(group.exceptions)
    return report
Reasoning
The counterexample changes the TypeError handler to KeyError. ValueError entries are still handled, but the TypeError remainder propagates as an ExceptionGroup. The verifier expects that concrete unhandled-group failure rather than accepting a partial-success report.
Download the reusable Python and packaging computer lab ↗

Independent practice

Add a total field equal to the two handled counts.

Add a total field equal to the two handled counts. Preserve separate category counts and the empty result. The first hint is to calculate the total after the except* handlers, when both known subsets have had the opportunity to update the report.

The second hint is to keep the flat-group assumption explicit. Summing the recorded immediate-child counts is correct for these fixtures, while recursively nested groups would require another traversal contract. Do not add a blanket handler that reports an incomplete total when an unexpected exception category remains unhandled.

Step 1: Count matching error leaves for this flat group and add the complete total without suppressing unexpected exception types. Step 2: Run the accepted baseline, edit the exercise, then check its independent solution cases; preserve caller inputs and the documented exception behavior.

Practice starter: solve before reading the solution

python

def inspect_example(kinds):
    errors = [ValueError('bad value') if kind == 'value' else TypeError('bad type')
              for kind in kinds]
    report = {'value': 0, 'type': 0}
    try:
        if errors:
            raise ExceptionGroup('owned batch', errors)
    except* ValueError as group:
        report['value'] = len(group.exceptions)
    except* TypeError as group:
        report['type'] = len(group.exceptions)
    return report

# Task: Count matching error leaves for this flat group and add the complete total without suppressing unexpected exception types.
# Practice contract: Count matching error leaves for this flat group and add the complete total without suppressing unexpected exception types.
Reasoning
Count matching error leaves for this flat group and add the complete total without suppressing unexpected exception types.
Download the reusable Python and packaging computer lab ↗
  • Hint 1: Add a total field equal to the two handled counts. Preserve separate category counts and the empty result. The first hint is to calculate the total after the except* handlers, when both known subsets have had the opportunity to update the report.
  • Hint 2: The second hint is to keep the flat-group assumption explicit. Summing the recorded immediate-child counts is correct for these fixtures, while recursively nested groups would require another traversal contract. Do not add a blanket handler that reports an incomplete total when an unexpected exception category remains unhandled.

Solution, tests and trade-offs

The solution calculates the total after all matching handlers finish.

The solution calculates the total after all matching handlers finish. Acceptance compares mixed, empty and type-only batches, while the controlled bug proves a missing type handler does not quietly become a complete report. Both grouping and propagation are observable behavior.

Exception grouping is useful with concurrent tasks and other multi-error operations, but it does not decide whether retrying an entire batch is safe. Keep partial work, cleanup and retry policy separate. A consumer may need the original structured failures rather than just counts, so this compact report should not be mistaken for a universal error-storage format.

Practice solution and acceptance checks

python

def inspect_example(kinds):
    errors = [ValueError('bad value') if kind == 'value' else TypeError('bad type')
              for kind in kinds]
    report = {'value': 0, 'type': 0}
    try:
        if errors:
            raise ExceptionGroup('owned batch', errors)
    except* ValueError as group:
        report['value'] = len(group.exceptions)
    except* TypeError as group:
        report['type'] = len(group.exceptions)
    report['total'] = report['value'] + report['type']
    return report
Reasoning
The solution calculates the total after all matching handlers finish. Acceptance compares mixed, empty and type-only batches, while the controlled bug proves a missing type handler does not quietly become a complete report. Both grouping and propagation are observable behavior.
Download the reusable Python and packaging computer lab ↗

Interview reasoning and next step

Explain what happens to the TypeError remainder in the counterexample after the ValueError subset is handled.

Explain what happens to the TypeError remainder in the counterexample after the ValueError subset is handled. Then distinguish an immediate child count from a recursive leaf count. A correct explanation should account for the empty batch without inventing an exception.

For a stretch, construct a nested owned ExceptionGroup and write a separate recursive leaf-counting function. Verify the resulting shape and counts explicitly. Keep that extension separate from the current flat contract so a future change cannot silently reinterpret previously published prepared results or make an incomplete handler look successful.

Optional stretch — after the core task works: For a stretch, construct a nested owned ExceptionGroup and write a separate recursive leaf-counting function. Verify the resulting shape and counts explicitly. Keep that extension separate from the current flat contract so a future change cannot silently reinterpret previously published prepared results or make an incomplete handler look successful.

Computer practice: https://applydjinn.com/typorithm/python-engineering-lab-20261107.zip. Use an isolated Python 3.13 environment, install requirements.txt, then run verify.py. Edit exercises/pye-exceptions.py and run --practice pye-exceptions. Fixture files are created in fresh temporary directories for every case. Packaging checks build real local artifacts, install the exact wheel without an index and run outside the source tree. No package is published to PyPI. The app includes static examples, prepared results and questions; it does not run Python or provide an editor.

Handle known failures in a flat batch while allowing unexpected exception types to propagate.