Cuando una excepción ocurre mientras se maneja otra, Python guarda el contexto automáticamente. La sintaxis raise X from Y permite hacer ese encadenamiento explícito y controlado, comunicando tanto el error nuevo como su causa original.
__context__ vs __cause__: la diferencia
# __context__: encadenamiento implícito (automático)
try:
int("abc")
except ValueError:
raise RuntimeError("fallo al parsear")
# Traceback mostrará:
# ValueError: invalid literal...
# During handling of the above exception, another exception occurred:
# RuntimeError: fallo al parsear
# __cause__: encadenamiento explícito con 'from'
try:
int("abc")
except ValueError as e:
raise RuntimeError("fallo al parsear") from e
# Traceback mostrará:
# ValueError: invalid literal...
# The above exception was the direct cause of the following exception:
# RuntimeError: fallo al parsear
raise X from None: suprimir el contexto
A veces el contexto original es un detalle de implementación que no debe filtrarse al usuario:
class ErrorConexion(Exception):
pass
def conectar_base_datos(dsn):
try:
import psycopg2
return psycopg2.connect(dsn)
except Exception as e:
# Suprimimos el contexto: el usuario no necesita ver el error
# interno de psycopg2; solo sabe que falló la conexión
raise ErrorConexion(f"No se pudo conectar: {dsn}") from None
try:
conectar_base_datos("postgresql://localhost/midb")
except ErrorConexion as e:
print(e) # No se pudo conectar: postgresql://localhost/midb
Ejemplo real: parseo de fechas
from datetime import datetime
class ErrorFechaInvalida(ValueError):
def __init__(self, texto, formatos):
self.texto = texto
self.formatos = formatos
super().__init__(
f"'{texto}' no coincide con ningún formato: {formatos}"
)
def parsear_fecha(texto, formatos=("%Y-%m-%d", "%d/%m/%Y", "%d-%m-%Y")):
for fmt in formatos:
try:
return datetime.strptime(texto, fmt)
except ValueError:
continue
raise ErrorFechaInvalida(texto, formatos)
try:
parsear_fecha("15-13-2026") # mes 13 no existe
except ErrorFechaInvalida as e:
print(e)
Ejemplo real: cliente HTTP con jerarquía
import urllib.request
import json
class ErrorHTTP(Exception):
pass
class ErrorRed(ErrorHTTP):
pass
class ErrorRespuesta(ErrorHTTP):
def __init__(self, codigo, url):
self.codigo = codigo
super().__init__(f"HTTP {codigo} al acceder a {url}")
def obtener_json(url):
try:
with urllib.request.urlopen(url, timeout=5) as resp:
datos = resp.read()
except OSError as e:
raise ErrorRed(f"No se pudo conectar a {url}") from e
try:
return json.loads(datos)
except json.JSONDecodeError as e:
raise ErrorRespuesta(200, url) from e
try:
datos = obtener_json("http://ejemplo.invalido/api")
except ErrorRed as e:
print(f"Problema de red: {e}")
print(f"Causa: {e.__cause__}")
except ErrorRespuesta as e:
print(f"Error HTTP {e.codigo}")
Inspeccionar la cadena de excepciones
def cadena_completa(exc):
partes = []
while exc:
partes.append(f"{type(exc).__name__}: {exc}")
exc = exc.__cause__ or exc.__context__
return " ? ".join(reversed(partes))
try:
try:
raise ValueError("valor incorrecto")
except ValueError as e:
raise RuntimeError("fallo de procesamiento") from e
except RuntimeError as e:
print(cadena_completa(e))
# ValueError: valor incorrecto ? RuntimeError: fallo de procesamiento
Usa raise X from Y cuando quieras documentar explícitamente la causa de un error, especialmente al cruzar capas de abstracción. Usa raise X from None cuando los detalles de implementación interna no deben exponerse al consumidor de tu librería.
