ExceptionGroup.split() es una herramienta de Python para dividir un grupo de excepciones en dos partes: las que cumplen una condición y las que no. Resulta especialmente útil en aplicaciones que ejecutan varias tareas, procesan registros por lotes o combinan respuestas de distintos servicios. En estos escenarios pueden ocurrir varias fallas al mismo tiempo, y conservarlas dentro de una estructura permite analizar el contexto completo sin perder información.
Qué representa ExceptionGroup
Un ExceptionGroup reúne varias excepciones relacionadas bajo un mismo mensaje. Imagina un proceso que valida muchos archivos. Uno puede no existir, otro puede tener permisos insuficientes y otro puede contener datos inválidos. En lugar de detenerse en el primer problema, el programa puede recopilar todas las fallas y lanzar un único grupo. Cada excepción individual sigue disponible para registro, diagnóstico, informe y recuperación.
Este modelo es muy útil en código concurrente, pero no se limita a la concurrencia. También funciona en importaciones de datos, validaciones, suites de pruebas, integraciones con APIs y cualquier flujo donde varias operaciones independientes puedan fallar por separado. El objetivo no es ocultar errores, sino organizarlos sin descartar detalles.
Cómo funciona split
El método split() recibe un tipo de excepción, una tupla de tipos o una función usada como filtro. Devuelve una tupla con dos elementos. El primero contiene las excepciones que coinciden con el criterio. El segundo contiene el resto. Cuando una de las partes no tiene elementos, Python devuelve None para esa posición.
grupo = ExceptionGroup(
"errores de validación",
[ValueError("edad inválida"), TypeError("tipo incorrecto")],
)
valores, otros = grupo.split(ValueError)
En este ejemplo, valores contiene el ValueError y otros contiene el TypeError. El grupo original no se modifica. Python crea grupos derivados y conserva el mensaje y la jerarquía relevante.
Filtrar varios tipos
También puedes pasar una tupla cuando distintos tipos de excepción pertenecen a la misma categoría operativa.
errores_archivo, restantes = grupo.split(
(FileNotFoundError, PermissionError)
)
Este patrón separa los problemas del sistema de archivos de los errores de validación o programación. Normalmente es más claro que recorrer manualmente todas las excepciones y construir listas por separado.
Usar una función como condición
La condición puede ser una función que recibe cada excepción y devuelve verdadero o falso. Esto sirve cuando la decisión depende de atributos personalizados, códigos de estado, indicadores de reintento o reglas del dominio.
class ErrorServicio(Exception):
def __init__(self, estado, mensaje):
super().__init__(mensaje)
self.estado = estado
def es_temporal(exc):
return isinstance(exc, ErrorServicio) and exc.estado >= 500
reintentos, definitivos = grupo.split(es_temporal)
Con este diseño, solo las fallas temporales se envían a una cola de reintentos. Las fallas definitivas quedan separadas para registro o corrección manual.
Ejemplo práctico con validación por lotes
Supongamos que una función valida muchos registros y debe informar todos los problemas encontrados, no solo el primero.
def validar_registros(registros):
errores = []
for indice, registro in enumerate(registros):
try:
if "email" not in registro:
raise KeyError(f"registro {indice}: falta email")
if registro.get("edad", 0) < 0:
raise ValueError(f"registro {indice}: edad inválida")
except Exception as exc:
errores.append(exc)
if errores:
raise ExceptionGroup("errores de registros", errores)
La capa que llama a la función puede separar los campos ausentes de los valores inválidos. De esta manera se muestran mensajes distintos al usuario y cada categoría se registra de forma apropiada.
try:
validar_registros(registros)
except ExceptionGroup as grupo:
ausentes, restantes = grupo.split(KeyError)
if ausentes:
registrar_campos_ausentes(ausentes)
if restantes:
raise restantes
El detalle más importante es conservar o volver a lanzar el grupo restante. Tratar una categoría conocida no debe eliminar silenciosamente otras fallas.
Grupos anidados
Los grupos pueden contener otros grupos. Esto ocurre cuando cada etapa de un pipeline o cada worker crea su propio conjunto de errores. El método split() conserva la estructura de árbol.
grupo = ExceptionGroup(
"pipeline",
[
ExceptionGroup("descarga", [TimeoutError("tiempo agotado")]),
ExceptionGroup("lectura", [ValueError("JSON inválido")]),
],
)
Después de separar por TimeoutError, el resultado sigue indicando que la excepción provino de la etapa de descarga. Ese contexto es esencial para diagnosticar flujos complejos.
split frente a subgroup
El método subgroup() devuelve únicamente la parte que coincide con el filtro. split() devuelve tanto las coincidencias como el resto. Usa subgroup() cuando solo necesitas consultar un subconjunto. Usa split() cuando necesitas procesar una categoría y continuar manejando o propagando las demás excepciones.
Relación con except*
Python también ofrece except* para manejar grupos de excepciones mediante sintaxis declarativa. Ambos enfoques se complementan. Usa except* cuando el flujo normal de manejo de errores resulta claro. Usa split() cuando el programa necesita almacenar, transformar, enrutar o inspeccionar grupos antes de decidir qué hacer.
Buenas prácticas
Prefiere filtrar por tipos o atributos estables. Los mensajes pueden cambiar, traducirse o incluir datos variables. Evita depender solo del texto. Mantén cada grupo enfocado en una operación o unidad lógica. Un grupo demasiado amplio con fallas no relacionadas resulta difícil de entender.
Registra suficiente contexto antes de aplicar acciones de recuperación. Son útiles el identificador de tarea, el número de registro, la ruta del archivo, el nombre del servicio y un identificador de correlación. No uses split() como mecanismo para ignorar errores. Cada excepción restante debe registrarse, devolverse, convertirse de forma intencional o propagarse.
Pruebas
Las pruebas deben verificar ambos grupos devueltos, los tipos incluidos y la conservación de la estructura anidada.
def test_split_por_tipo():
grupo = ExceptionGroup(
"prueba",
[ValueError("a"), TypeError("b")],
)
valores, otros = grupo.split(ValueError)
assert valores is not None
assert otros is not None
assert isinstance(valores.exceptions[0], ValueError)
assert isinstance(otros.exceptions[0], TypeError)
También conviene probar una condición sin coincidencias y otra donde todas las excepciones coinciden. Estos casos confirman cuándo uno de los elementos devueltos es None.
Cuándo usarlo
ExceptionGroup.split() es una buena opción para procesamiento por lotes, tareas concurrentes, pipelines de datos, llamadas a múltiples APIs, validaciones de formularios, importaciones y pruebas compuestas. En funciones simples que solo pueden producir una falla a la vez, el manejo tradicional suele ser suficiente y más fácil de leer.
Errores comunes
Un error frecuente es asumir que el resultado es una lista plana. Sigue siendo una estructura de grupos. Otro error es tratar las coincidencias y olvidar el resto. También es problemático crear un grupo enorme para subsistemas no relacionados. Los grupos pequeños y contextuales generan registros más claros y una recuperación más segura.
Conclusión
ExceptionGroup.split() ofrece una forma precisa de clasificar múltiples fallas sin perder su origen. Separa excepciones por tipo o condición, conserva grupos anidados y permite recuperar solo aquello que la aplicación conoce. Continúa aprendiendo con los artículos de Academify sobre try y except, traceback, asyncio y errores comunes. Consulta también la documentación oficial y la PEP 654.







