Manejo de Excepciones en Python: Try, Except, Finally y Raise

👤 Admin 📅 8 de noviembre, 2025 ⏱ 16 min 🏷 Python Intermedio

¿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.py

Contexto 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 -> None

Buenas Prácticas

  • Sé específico: Captura excepciones específicas, no uses except Exception para 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.