¿Qué son las Excepciones?
Las excepciones son eventos que interrumpen el flujo normal del programa debido a errores. En lugar de que tu programa se detenga completamente, puedes capturar y manejar estos errores de manera controlada.
Sintaxis Básica: Try-Except
# Sin manejo de excepciones
numero = int(input("Ingresa un número: "))
print(f"El doble es: {numero * 2}")
# Si el usuario ingresa "abc", el programa se detiene con error
# Con manejo de excepciones
try:
numero = int(input("Ingresa un número: "))
print(f"El doble es: {numero * 2}")
except ValueError:
print("Error: Debes ingresar un número válido")Tipos Comunes de Excepciones
# ValueError - Valor incorrecto
try:
numero = int("abc") # No se puede convertir texto a int
except ValueError:
print("Error de valor")
# ZeroDivisionError - División por cero
try:
resultado = 10 / 0
except ZeroDivisionError:
print("No se puede dividir por cero")
# TypeError - Tipo incorrecto
try:
resultado = "5" + 5 # No se puede sumar string con int
except TypeError:
print("Error de tipo")
# KeyError - Clave no existe
try:
diccionario = {"nombre": "Ana"}
print(diccionario["edad"])
except KeyError:
print("La clave no existe")
# IndexError - Índice fuera de rango
try:
lista = [1, 2, 3]
print(lista[10])
except IndexError:
print("Índice fuera de rango")
# FileNotFoundError - Archivo no encontrado
try:
archivo = open("archivo_inexistente.txt")
except FileNotFoundError:
print("Archivo no encontrado")Capturar Múltiples Excepciones
# Método 1: Bloques separados
try:
numero = int(input("Número: "))
resultado = 10 / numero
print(resultado)
except ValueError:
print("Error: Ingresa un número válido")
except ZeroDivisionError:
print("Error: No se puede dividir por cero")
# Método 2: Tupla de excepciones
try:
numero = int(input("Número: "))
resultado = 10 / numero
except (ValueError, ZeroDivisionError) as e:
print(f"Error: {e}")
# Método 3: Excepción genérica (usar con cuidado)
try:
numero = int(input("Número: "))
resultado = 10 / numero
except Exception as e:
print(f"Ocurrió un error: {e}")Else - Código sin Errores
El bloque else se ejecuta solo si no hubo excepciones:
try:
numero = int(input("Ingresa un número: "))
except ValueError:
print("Número inválido")
else:
print(f"Número válido: {numero}")
print("Este código solo se ejecuta si no hubo error")Finally - Siempre se Ejecuta
El bloque finally se ejecuta siempre, haya o no excepciones:
try:
archivo = open("datos.txt", "r")
contenido = archivo.read()
print(contenido)
except FileNotFoundError:
print("Archivo no encontrado")
finally:
# Este código SIEMPRE se ejecuta
print("Cerrando recursos...")
try:
archivo.close()
except:
pass
# Ejemplo práctico: Conexión a base de datos
try:
conexion = conectar_base_datos()
datos = consultar(conexion)
procesar(datos)
except Exception as e:
print(f"Error en la operación: {e}")
finally:
# Aseguramos que la conexión se cierre siempre
if conexion:
conexion.close()Raise - Lanzar Excepciones
Puedes lanzar excepciones manualmente con raise:
# Lanzar excepción simple
def dividir(a, b):
if b == 0:
raise ValueError("El divisor no puede ser cero")
return a / b
try:
resultado = dividir(10, 0)
except ValueError as e:
print(f"Error: {e}")
# Validación de edad
def registrar_usuario(nombre, edad):
if edad < 18:
raise ValueError("Debes ser mayor de 18 años")
if not nombre:
raise ValueError("El nombre no puede estar vacío")
print(f"Usuario {nombre} registrado exitosamente")
try:
registrar_usuario("Ana", 16)
except ValueError as e:
print(f"Error de registro: {e}")Excepciones Personalizadas
Puedes crear tus propias excepciones heredando de Exception:
# Definir excepción personalizada
class SaldoInsuficienteError(Exception):
def __init__(self, saldo, monto):
self.saldo = saldo
self.monto = monto
mensaje = f"Saldo insuficiente. Tienes ${saldo} pero intentas retirar ${monto}"
super().__init__(mensaje)
class CuentaBancaria:
def __init__(self, titular, saldo):
self.titular = titular
self.saldo = saldo
def retirar(self, monto):
if monto > self.saldo:
raise SaldoInsuficienteError(self.saldo, monto)
self.saldo -= monto
return f"Retiro exitoso. Saldo: ${self.saldo}"
# Usar la excepción personalizada
cuenta = CuentaBancaria("Ana", 1000)
try:
print(cuenta.retirar(500)) # OK
print(cuenta.retirar(800)) # Error
except SaldoInsuficienteError as e:
print(f"Error: {e}")Assert - Afirmaciones
Las afirmaciones verifican condiciones durante el desarrollo:
def calcular_promedio(numeros):
assert len(numeros) > 0, "La lista no puede estar vacía"
assert all(isinstance(n, (int, float)) for n in numeros), "Todos deben ser números"
return sum(numeros) / len(numeros)
try:
print(calcular_promedio([10, 20, 30])) # OK
print(calcular_promedio([])) # AssertionError
except AssertionError as e:
print(f"Error de afirmación: {e}")
# Nota: assert se puede desactivar con -O
# python -O script.pyContexto Completo: Try-Except-Else-Finally
def procesar_archivo(nombre_archivo):
archivo = None
try:
print("Intentando abrir archivo...")
archivo = open(nombre_archivo, 'r')
contenido = archivo.read()
numero = int(contenido)
resultado = 100 / numero
except FileNotFoundError:
print(f"Error: El archivo {nombre_archivo} no existe")
return None
except ValueError:
print("Error: El contenido no es un número válido")
return None
except ZeroDivisionError:
print("Error: El número no puede ser cero")
return None
except Exception as e:
print(f"Error inesperado: {e}")
return None
else:
# Solo si no hubo errores
print("Archivo procesado exitosamente")
return resultado
finally:
# Siempre se ejecuta
if archivo:
archivo.close()
print("Archivo cerrado")
resultado = procesar_archivo("numeros.txt")Traceback - Información del Error
import traceback
try:
resultado = 10 / 0
except ZeroDivisionError:
# Imprimir el traceback completo
traceback.print_exc()
# Obtener traceback como string
error_info = traceback.format_exc()
print("Error capturado:", error_info)Logging de Errores
import logging
# Configurar logging
logging.basicConfig(
filename='errores.log',
level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def dividir_numeros(a, b):
try:
resultado = a / b
return resultado
except ZeroDivisionError as e:
logging.error(f"División por cero: {a}/{b}")
logging.exception("Detalles del error:") # Incluye traceback
return None
except Exception as e:
logging.error(f"Error inesperado: {e}")
return None
print(dividir_numeros(10, 0))
print(dividir_numeros(10, 2))Ejemplos Prácticos
1. Validador de Entrada
def obtener_numero_entero(mensaje, minimo=None, maximo=None):
while True:
try:
numero = int(input(mensaje))
if minimo is not None and numero < minimo:
raise ValueError(f"El número debe ser mayor o igual a {minimo}")
if maximo is not None and numero > maximo:
raise ValueError(f"El número debe ser menor o igual a {maximo}")
return numero
except ValueError as e:
print(f"Error: {e}. Intenta de nuevo.")
# Usar la función
edad = obtener_numero_entero("Ingresa tu edad (18-100): ", 18, 100)
print(f"Edad válida: {edad}")2. Sistema de Retry
import time
def conectar_servidor(intentos_maximos=3):
for intento in range(1, intentos_maximos + 1):
try:
print(f"Intento {intento} de {intentos_maximos}...")
# Simular conexión que puede fallar
import random
if random.random() < 0.7: # 70% de probabilidad de fallar
raise ConnectionError("No se pudo conectar al servidor")
print("¡Conexión exitosa!")
return True
except ConnectionError as e:
if intento == intentos_maximos:
print(f"Error después de {intentos_maximos} intentos: {e}")
return False
print(f"Error: {e}. Reintentando en 2 segundos...")
time.sleep(2)
conectar_servidor()3. Procesador de Lista Seguro
def procesar_lista_segura(lista, operacion):
resultados = []
errores = []
for i, elemento in enumerate(lista):
try:
resultado = operacion(elemento)
resultados.append(resultado)
except Exception as e:
errores.append({
'indice': i,
'elemento': elemento,
'error': str(e)
})
return resultados, errores
# Usar la función
numeros = ['10', '20', 'abc', '30', 'xyz', '40']
def convertir_a_int(x):
return int(x)
resultados, errores = procesar_lista_segura(numeros, convertir_a_int)
print("Resultados exitosos:", resultados)
print("\nErrores encontrados:")
for error in errores:
print(f"Índice {error['indice']}: {error['elemento']} - {error['error']}")4. Context Manager Personalizado
class ManejarArchivo:
def __init__(self, nombre_archivo, modo):
self.nombre_archivo = nombre_archivo
self.modo = modo
self.archivo = None
def __enter__(self):
try:
self.archivo = open(self.nombre_archivo, self.modo)
return self.archivo
except Exception as e:
raise Exception(f"Error al abrir archivo: {e}")
def __exit__(self, exc_type, exc_val, exc_tb):
if self.archivo:
self.archivo.close()
if exc_type is not None:
print(f"Se produjo un error: {exc_val}")
return False # Propagar la excepción
return True
# Usar el context manager
try:
with ManejarArchivo('datos.txt', 'r') as archivo:
contenido = archivo.read()
print(contenido)
except Exception as e:
print(f"Error manejado: {e}")5. Decorador para Manejo de Errores
def manejar_errores(funcion):
def wrapper(*args, **kwargs):
try:
return funcion(*args, **kwargs)
except Exception as e:
print(f"Error en {funcion.__name__}: {e}")
return None
return wrapper
@manejar_errores
def dividir(a, b):
return a / b
@manejar_errores
def procesar_datos(datos):
resultado = datos['valor'] * 2
return resultado
print(dividir(10, 2)) # 5.0
print(dividir(10, 0)) # Error en dividir: division by zero -> None
print(procesar_datos({'clave': 10})) # Error -> NoneBuenas Prácticas
- Sé específico: Captura excepciones específicas, no uses
except Exceptionpara todo - No silencies errores: Al menos registra los errores que capturas
- Usa finally: Para limpieza de recursos (cerrar archivos, conexiones)
- Mensajes claros: Proporciona mensajes de error útiles
- No abuses de try-except: No lo uses para controlar flujo normal del programa
- Documenta: Indica qué excepciones puede lanzar tu función
Antipatrones a Evitar
# MAL: Capturar todo sin hacer nada
try:
codigo_peligroso()
except:
pass # ¡Nunca hagas esto!
# MAL: Excepciones muy generales
try:
procesar_datos()
except Exception: # Muy genérico
print("Error")
# BIEN: Específico y con logging
try:
procesar_datos()
except ValueError as e:
logging.error(f"Error de valor: {e}")
except FileNotFoundError as e:
logging.error(f"Archivo no encontrado: {e}")Conclusión
El manejo adecuado de excepciones es crucial para crear aplicaciones robustas y confiables. Permite que tu código maneje errores graciosamente en lugar de fallar abruptamente. Usa try-except de manera inteligente, crea excepciones personalizadas cuando sea necesario y siempre proporciona información útil sobre los errores.