ExceptionGroup.split() is a Python feature for separating a group of exceptions into two parts: exceptions that match a condition and exceptions that do not. It is particularly useful in applications that run several tasks, process many records, or combine results from multiple services. In these scenarios, more than one operation can fail at the same time. Keeping those failures in a structured group makes it possible to inspect the full picture without losing context.
What ExceptionGroup represents
An ExceptionGroup stores multiple related exceptions under one message. Consider a batch that validates many files. One file may be missing, another may have insufficient permissions, and another may contain invalid data. Instead of stopping at the first problem, the program can collect the failures and raise one group. Every individual exception remains available for logging, diagnosis, reporting, and recovery.
This model is valuable in concurrent code, but it is not limited to concurrency. It also works well for data imports, validation pipelines, test suites, API aggregation, and any workflow where independent operations can fail separately. The purpose is not to hide exceptions. The purpose is to organize them while preserving evidence about every failure.
How split works
The split() method accepts an exception type, a tuple of exception types, or a callable used as a filter. It returns a two-item tuple. The first item contains matching exceptions, while the second item contains everything else. If one side has no exceptions, that side is returned as None.
group = ExceptionGroup(
"validation failures",
[ValueError("invalid age"), TypeError("wrong type")],
)
value_errors, others = group.split(ValueError)
In this example, value_errors contains the ValueError, and others contains the TypeError. The original group is not modified. Python creates derived groups while preserving the relevant message and hierarchy.
Filtering multiple exception types
You can pass a tuple when several exception types belong to the same operational category.
file_errors, remaining = group.split(
(FileNotFoundError, PermissionError)
)
This pattern separates file-system problems from validation or programming errors. It is usually clearer than manually iterating through every exception and building lists yourself.
Using a callable as the condition
The condition may also be a function that receives each exception and returns True or False. This is useful when the decision depends on custom attributes, status codes, retry flags, or domain-specific rules.
class ServiceError(Exception):
def __init__(self, status, message):
super().__init__(message)
self.status = status
def is_temporary(exc):
return isinstance(exc, ServiceError) and exc.status >= 500
retry_group, permanent_group = group.split(is_temporary)
With this design, only temporary service failures are sent to a retry queue. Permanent failures remain available for reporting or manual correction.
Practical batch-validation example
Suppose a function validates many records and should report every problem found rather than only the first one.
def validate_records(records):
errors = []
for index, record in enumerate(records):
try:
if "email" not in record:
raise KeyError(f"record {index}: missing email")
if record.get("age", 0) < 0:
raise ValueError(f"record {index}: invalid age")
except Exception as exc:
errors.append(exc)
if errors:
raise ExceptionGroup("record validation errors", errors)
The calling layer can separate missing-field errors from invalid-value errors. Different messages can then be shown to users, and each category can be logged in a suitable format.
try:
validate_records(records)
except ExceptionGroup as group:
missing, remaining = group.split(KeyError)
if missing:
log_missing_fields(missing)
if remaining:
raise remaining
The most important detail is to preserve or re-raise the remaining group. Handling one known category must not silently discard unrelated failures.
Nested exception groups
Exception groups may contain other groups. This commonly happens when each pipeline stage or worker creates its own group. The split() method preserves that tree structure.
group = ExceptionGroup(
"pipeline",
[
ExceptionGroup("download", [TimeoutError("timed out")]),
ExceptionGroup("parsing", [ValueError("invalid JSON")]),
],
)
After splitting by TimeoutError, the matching result still shows that the exception came from the download stage. That context is essential when diagnosing complex workflows.
split compared with subgroup
The subgroup() method returns only the matching portion. The split() method returns both the matching portion and the remainder. Choose subgroup() when you only need to inspect one subset. Choose split() when you need to process one category and continue handling or re-raising everything else.
Relationship with except star
Python also provides except* for handling exception groups declaratively. The two approaches are complementary. Use except* when normal exception-handling syntax expresses the flow clearly. Use split() when the program needs to store, transform, route, or inspect groups programmatically before deciding what to do.
Good practices
Prefer filtering by stable exception types or attributes. Error messages can change, may be translated, and sometimes include variable data. Avoid using text as the only criterion. Keep groups focused on one operation or logical unit. A very broad group containing unrelated failures becomes difficult to understand.
Log enough context before applying recovery steps. Useful fields include a task identifier, record number, file path, service name, and correlation identifier. Do not use split() as a mechanism for silently ignoring errors. Every remaining exception should be logged, returned, converted intentionally, or re-raised.
Testing split behavior
Tests should verify both returned groups, the exception types inside them, and the preservation of nested structure.
def test_split_by_type():
group = ExceptionGroup(
"test",
[ValueError("a"), TypeError("b")],
)
values, others = group.split(ValueError)
assert values is not None
assert others is not None
assert isinstance(values.exceptions[0], ValueError)
assert isinstance(others.exceptions[0], TypeError)
Also test a condition with no matches and a condition where every exception matches. These cases confirm when one side of the returned tuple becomes None.
When this method is useful
ExceptionGroup.split() is a strong choice for batch processing, concurrent tasks, data pipelines, API fan-out, form validation, imports, and composite tests. For simple functions that can only fail once at a time, traditional exception handling is often sufficient and easier to read.
Common mistakes
A frequent mistake is assuming the result is a flat list. It is still an exception-group structure. Another mistake is treating the matching group but forgetting the remainder. A third mistake is creating one enormous group for unrelated subsystems. Smaller, contextual groups produce clearer logs and safer recovery logic.
Conclusion
ExceptionGroup.split() provides a precise way to classify multiple failures without losing their origin. It separates exceptions by type or condition, preserves nested groups, and supports selective recovery while keeping unknown failures visible. Continue learning with Academify articles about try and except, traceback, asyncio, and common Python errors. See the official Python documentation and PEP 654 for the formal behavior.







