Raising & Chaining
Traduce fallos de bajo nivel a errores de dominio sin perder contexto. Usa raise ... from, raise desnudo y from None intencionadamente para que los tracebacks cuenten la historia completa.
Receta
def load_user(user_id: str) -> dict:
try:
return fetch_row(user_id)
except KeyError as exc:
raise UserNotFoundError(user_id) from excCuándo usar esto:
- Envolver excepciones de terceros en los límites de la API
- Añadir contexto de negocio preservando la causa raíz
- Suprimir tracebacks intermedios ruidosos (
from None) - Registrar y luego volver a lanzar con
raisedesnudo - Encadenar errores de validación en arquitecturas en capas
Ejemplo de Trabajo
class UserNotFoundError(LookupError):
def __init__(self, user_id: str) -> None:
self.user_id = user_id
super().__init__(f"user not found: {user_id}")
_DB: dict[str, dict] = {"1": {"name": "Ada"}}
def fetch_row(user_id: str) -> dict:
return _DB[user_id]
def get_user(user_id: str) -> dict:
try:
return fetch_row(user_id)
except KeyError as exc:
raise UserNotFoundError(user_id) from exc
def parse_id(raw: str) -> str:
try:
value = int(raw)
except ValueError:
raise ValueError(f"invalid id: {raw!r}") from None
if value < 1:
raise ValueError("id must be positive")
return str(value)
try:
get_user("999")
except UserNotFoundError as exc:
print(exc)
print(exc.__cause__)Lo que esto demuestra:
from excestablece__cause__para depurar cadenasfrom Noneoculta los detalles internos del analizador a los consumidores de la API- Las excepciones de dominio transportan identificadores para manejadores y registros
- Los
raisede validación desnudos aún producen mensajes claros
Inmersión Profunda
Cómo Funciona
raise NewError()inicia una nueva excepción en el punto actual del tracebackraise NewError() from excenlazaexccomo__cause__raisedesnudo dentro deexceptvuelve a lanzar la excepción capturada en tránsitoraise excdesde una variable almacenada puede restablecer el contexto del traceback; prefiereraisedesnudoException.__context__puede diferir de__cause__cuando ocurre un encadenamiento implícito
Opciones de Encadenamiento
| Sintaxis | Efecto |
|---|---|
raise A from b | Causa explícita mostrada en el traceback |
raise (desnudo) | La misma excepción continúa |
raise A from None | Suprime el contexto mostrado |
Trampas Comunes
- Perder la causa con
raise New()sinfrom: el contexto implícito aún puede aparecer pero es ambiguo. Solución: usa siemprefrom excal envolver. - Ignorar con
from Noneen todas partes: oculta las causas raíz en operaciones. Solución: reservafrom Nonepara los límites de análisis de entrada del usuario. raise excdespués de que termina el bloqueexcept: el traceback apunta al sitio de re-lanzamiento. Solución:raisedesnudo solo dentro de unexceptactivo.- Bucles de encadenamiento: A desde B desde A en envoltorios recursivos. Solución: lanza el error de dominio una vez en el límite.
- Registrar
exc_infoy luego lanzar un tipo diferente sinfrom: los registros y el traceback no coinciden. Solución: encadena o registra dentro del manejador que vuelve a lanzar desnudo.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Tipos Result/Either | Núcleo funcional, sin excepciones | Límites de servicio de Python que esperan excepciones |
| Códigos de error | Extensiones C, protocolos | Capas de aplicación de Python |
| Agregado en ExceptionGroup | Fallos paralelos | Ruta de fallo única |
Preguntas Frecuentes
¿Cuándo debo usar from None?
Cuando la excepción subyacente es un detalle de implementación para los llamadores; típicamente, el análisis de entrada en los bordes de la API pública.
¿Qué es __context__ vs __cause__?
__cause__ es from explícito. __context__ es un enlace implícito al lanzar mientras se maneja otra excepción.
¿Debo envolver todos los KeyError?
Solo en los límites del módulo. El código interno puede dejar que KeyError se propague dentro de un paquete.
¿Cómo usan los registradores la información de excepción?
log.exception("msg") o exc_info=True capturan el traceback de la excepción activa en el momento del registro.
¿Puedo encadenar excepciones personalizadas?
Sí. Hereda de Exception, lanza con from, y añade campos a la clase para un manejo estructurado.
¿Interactúa finally con el encadenamiento?
finally se ejecuta antes de que la excepción se propague; el encadenamiento establecido en except se preserva.
¿Cómo mapean los frameworks las excepciones?
Los manejadores de FastAPI/Flask mapean tipos de excepción a respuestas HTTP; encadena a la causa raíz para registros 500.
¿Debo volver a lanzar después del agotamiento de reintentos?
Sí - raise last_exc from last_exc o simplemente lanza el fallo final con el contexto del número de intentos.
¿Qué pasa con assert?
assert se convierte en AssertionError; no es para validación en tiempo de ejecución en producción; usa raise explícitos.
¿Cómo pruebo las cadenas de excepciones?
pytest.raises y verifica que exc.value.__cause__ sea del tipo esperado.
Relacionado
- Jerarquías de Excepciones Personalizadas - tipos de dominio
- Conceptos Básicos de Excepciones - estructura try/except
- Reintentos y Backoff - lanzar después de reintentos
- Mejores Prácticas de Errores y Logging - reglas operativas
Versiones de Stack: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.