Python es fácil de escribir, pero un manejo descuidado de errores puede convertir una aplicación funcional en una que falla silenciosamente en producción. Sin stack trace, sin alerta — solo una función que dejó de trabajar hace tres días y nadie lo notó.
Este artículo cubre 7 patrones de manejo de errores que realmente usarás en el trabajo. Cada fragmento funciona con Python 3.6+ estándar, sin dependencias.
Antes de empezar: el manejo de excepciones no es algo que se añade al final. Es parte del diseño. El mejor código no previene los errores — se asegura de que los errores no tumben el sistema.
1. try / except básico — Empieza aquí
La base de todo manejo de errores en Python. Captura tipos específicos de excepciones y responde a cada uno de manera diferente.
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return "No se puede dividir por cero"
except TypeError:
return "Por favor ingresa números"
print(divide(10, 2)) # 5.0
print(divide(10, 0)) # No se puede dividir por cero
print(divide(10, "a")) # Por favor ingresa números
La regla de oro: nunca escribas un except: vacío. Siempre especifica el tipo de excepción.
El error clásico de principiante:
# NO hagas esto
except Exception:
pass
Esto silencia cada error. Como mínimo, registra la excepción:
except ZeroDivisionError as e:
logging.error("División fallida: %s", e)
Conclusión: Captura excepciones por tipo. Trata except: pass como una bomba de tiempo.
2. finally — Limpieza garantizada
Cuando abres un archivo, una conexión a base de datos o un socket, necesitas cerrarlo — sin importar si la operación tuvo éxito o no.
f = None
try:
f = open("data.txt", "w")
f.write("hello")
except IOError as e:
print("Escritura fallida:", e)
finally:
if f:
f.close()
print("Limpieza completada")
El bloque finally se ejecuta pase lo que pase.
Error común: escribir f.close() sin verificar si f fue creado.
Historia de terror: olvidar cerrar un file handle en un proceso batch. El archivo queda bloqueado. A la mañana siguiente, el job programado falla.
Conclusión: Usa finally para limpieza. Siempre verifica que el recurso fue creado.
3. La sentencia with — Cómo lo hacen los profesionales
En la práctica, rara vez escribirás finally: f.close() a mano. La sentencia with lo hace automáticamente.
try:
with open("data.txt", "w") as f:
f.write("hello")
except IOError as e:
print("Escritura fallida:", e)
Puedes abrir múltiples archivos en una sola sentencia:
with open("input.txt") as src, open("output.txt", "w") as dst:
dst.write(src.read())
Conclusión: Manejo de archivos = with. Sin excepciones.
4. Re-lanzar excepciones con raise
A veces necesitas registrar un error y dejar que se propague.
import logging
def process(data):
try:
return transform(data)
except Exception as e:
logging.error("Procesamiento fallido: %s", e)
raise
Sin el raise, la función retorna None silenciosamente.
Para agregar contexto, usa encadenamiento de excepciones:
class ProcessingError(Exception):
pass
try:
result = transform(data)
except ValueError as e:
raise ProcessingError("Datos de entrada inválidos") from e
Conclusión: Log-and-swallow es una fábrica de bugs.
5. Excepciones personalizadas — Errores con significado
Las excepciones integradas son genéricas. Las excepciones personalizadas hacen tu código auto-documentado.
class ValidationError(Exception):
"""Se lanza cuando los datos no pasan validación."""
pass
class APIError(Exception):
def __init__(self, status_code, message):
self.status_code = status_code
super().__init__(f"{status_code}: {message}")
def validate_age(age):
if age < 0:
raise ValidationError("La edad no puede ser negativa")
return age
try:
validate_age(-5)
except ValidationError as e:
print(e)
Reglas prácticas:
• Siempre hereda de Exception, nunca de BaseException.
• Nombres descriptivos: PaymentDeclinedError supera a Error1.
• No crees docenas de excepciones para un proyecto pequeño.
Conclusión: Las excepciones personalizadas son herramientas de diseño.
6. assert — Verificaciones solo para desarrollo
assert verifica supuestos durante el desarrollo. Si la condición es falsa, lanza AssertionError.
def withdraw(balance, amount):
assert amount > 0, "El monto debe ser positivo"
assert balance >= amount, "Fondos insuficientes"
return balance - amount
print(withdraw(100, 50)) # 50
print(withdraw(100, 200)) # AssertionError
Lo crítico: assert puede ser deshabilitado con python -O.
Para validación en producción:
def withdraw(balance, amount):
if amount <= 0:
raise ValueError("El monto debe ser positivo")
if balance < amount:
raise ValueError("Fondos insuficientes")
return balance - amount
Conclusión: Assert es una herramienta de desarrollo, no un guardia de producción.
7. Lógica de reintentos — Porque las redes fallan
Las llamadas a API expiran. Las conexiones a BD se caen. El reintento no es opcional.
import time
import random
def call_api():
if random.random() < 0.7:
raise ConnectionError("Servidor no disponible")
return {"status": "ok"}
max_retries = 5
for attempt in range(max_retries):
try:
result = call_api()
print("Éxito:", result)
break
except ConnectionError as e:
wait = 2 ** attempt
print(f"Intento {attempt + 1} falló, reintentando en {wait}s...")
time.sleep(wait)
else:
print("Todos los reintentos agotados")
Reglas para reintentos en producción:
• Siempre establece un máximo de reintentos.
• Usa backoff exponencial.
• Solo reintenta errores transitorios.
• La construcción for/else es perfecta para esto.
Conclusión: Todo código que toque la red necesita reintentos. Punto.
Resumen: El manejo de errores es diseño
El kit completo:
1. try/except — Captura específica.
2. finally — Limpieza garantizada.
3. with — Gestión Pythónica de recursos.
4. raise — No tragues errores.
5. Excepciones personalizadas — Tipos auto-documentados.
6. assert — Solo para desarrollo.
7. Reintentos — Obligatorio para red.
El manejo de excepciones no se trata de prevenir errores — se trata de asegurar que los errores no tumben el sistema.

Deja una respuesta