Encadena siempre las excepciones con 'from e'
Dos palabras en Python, from e, salvan tu sesión de depuración a las 2 de la mañana. Atrapas un error, lo envuelves en uno más claro, lo relanzas — y si omites la cláusula from, el traceback que lees de guardia a las 2am apunta al envoltorio, no a la línea que realmente falló. El encadenamiento de excepciones existe en el lenguaje desde Python 3.0 (PEP 3134) y te cuesta exactamente dos palabras.
El modo de falla
Este es el patrón que pierde información:
try:
config = json.loads(raw)
except json.JSONDecodeError:
raise ConfigError("invalid config file") # original details buried
Python sí conserva la excepción original aquí — como contexto implícito, impresa bajo "During handling of the above exception, another exception occurred". Pero ese mensaje significa "algo más explotó mientras se manejaba un error", lo cual es ambiguo: ¿la segunda excepción fue intencional o un bug en tu bloque except? Peor aún, las herramientas que solo reportan la excepción final (muchos agregadores de logs, rastreadores de errores con configuración por defecto) te muestran ConfigError: invalid config file y nada más.
El arreglo de dos palabras
try:
config = json.loads(raw)
except json.JSONDecodeError as e:
raise ConfigError("invalid config file") from e
Ahora Python adjunta la excepción original como __cause__ en la nueva, y el traceback imprime ambas, unidas por una línea inequívoca:
json.decoder.JSONDecodeError: Expecting ',' delimiter: line 3 column 9
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
ConfigError: invalid config file
El envoltorio le da contexto a quien lee ("falló la carga de la configuración"); la causa encadenada le da la causa raíz, el archivo y la línea. Sin adivinar, sin volver a correr con prints.
Los tres modos de encadenamiento
| Escribes | Python define | El traceback dice |
|---|---|---|
raise NewError(...) dentro de except | __context__ (implícito) | "During handling of the above exception, another exception occurred" |
raise NewError(...) from e | __cause__ (explícito) | "The above exception was the direct cause of the following exception" |
raise NewError(...) from None | suprime el contexto | Solo la nueva excepción — la original queda oculta |
Reglas prácticas: usa from e siempre que la nueva excepción sea una traducción deliberada de la anterior (el caso más común por lejos). Usa from None solo cuando la original sea puro ruido — por ejemplo, un KeyError dentro de un helper de búsqueda donde "no encontrado" es toda la historia. Un raise a secas (sin argumento) relanza la excepción actual intacta y no necesita encadenamiento.
Notas para usuarios avanzados
- Ponle un linter. Ruff y flake8-bugbear marcan un
raisesinfromdentro de un bloqueexceptcomo B904 (raise-without-from-inside-except); Pylint tiene el equivalenteraise-missing-from. Activa uno y este tip se hace cumplir solo — incluso sobre código generado por IA, que emite con frecuencia el patrón sin encadenar. - Inspecciona la cadena por código.
err.__cause__(explícito) yerr.__context__(implícito) son atributos normales — un middleware de reporte de errores puede recorrer la cadena y registrar cada eslabón, y__cause__es escribible si necesitas adjuntar una causa después. fromtambién acepta una clase.raise TimeoutError("slow upstream") from ConnectionErrorinstancia la clase y adjunta la instancia como__cause__— útil en tests y adaptadores pequeños.__suppress_context__es lo quefrom Nonerealmente define. Como es solo un atributo, puedes activarlo en una excepción ya capturada antes de relanzarla.- Ángulo de agentes de IA: un agente de código que depura tu proyecto lee el mismo traceback que tú. Un traceback encadenado le entrega la causa raíz de una sola vez; un envoltorio sin encadenar lo manda (junto con su presupuesto de tokens) a cazarla.
Recursos
- Tutorial de Python — Exception Chaining
- La sentencia
raise—from,__cause__,__context__ - PEP 3134 — Exception Chaining and Embedded Tracebacks
- Regla B904 de Ruff — raise-without-from-inside-except
- Excepciones integradas —
BaseException.__cause__y compañía
Mira la versión de 60 segundos — reel #126 de la serie Yeda AI Tips — y síguenos para un tip concreto de AI coding por reel.