📚 Unidad 9. Manejo de Excepciones
Las excepciones son errores que ocurren durante la ejecución del programa. El manejo de excepciones permite controlar estos errores de forma elegante.
9.1. ¿Qué son las Excepciones?
Cuando ocurre un error en Python, se genera una excepción que detiene el programa.
# Error de división por cero
resultado = 10 / 0 # ZeroDivisionError
# Error de índice
lista = [1, 2, 3]
print(lista[10]) # IndexError
# Error de tipo
numero = "texto" + 5 # TypeError
# Error de clave
diccionario = {"a": 1}
print(diccionario["b"]) # KeyError
# Error de archivo
archivo = open("no_existe.txt") # FileNotFoundError
9.2. Try / Except
Sintaxis Básica
try:
# Código que puede causar error
resultado = 10 / 0
except:
# Se ejecuta si hay error
print("¡Ocurrió un error!")
Capturar Excepciones Específicas
try:
numero = int(input("Introduce un número: "))
resultado = 100 / numero
print(f"Resultado: {resultado}")
except ValueError:
print("Error: Debes introducir un número válido")
except ZeroDivisionError:
print("Error: No se puede dividir por cero")
Capturar Múltiples Excepciones
try:
lista = [1, 2, 3]
indice = int(input("Índice: "))
valor = lista[indice]
resultado = 10 / valor
except (ValueError, IndexError) as e:
print(f"Error de entrada: {e}")
except ZeroDivisionError:
print("Error: División por cero")
Obtener Información del Error
try:
resultado = 10 / 0
except ZeroDivisionError as e:
print(f"Tipo de error: {type(e).__name__}")
print(f"Mensaje: {e}")
9.3. Else y Finally
Bloque else
Se ejecuta si NO hubo excepciones.
try:
numero = int(input("Número: "))
resultado = 100 / numero
except ValueError:
print("Error: No es un número válido")
except ZeroDivisionError:
print("Error: No se puede dividir por cero")
else:
# Se ejecuta solo si no hubo errores
print(f"El resultado es: {resultado}")
Bloque finally
Se ejecuta SIEMPRE, haya o no excepciones.
try:
archivo = open("datos.txt", "r")
contenido = archivo.read()
except FileNotFoundError:
print("Error: Archivo no encontrado")
finally:
# Siempre se ejecuta
print("Operación finalizada")
# Aquí se suelen cerrar recursos
Ejemplo Completo
def dividir(a, b):
try:
resultado = a / b
except ZeroDivisionError:
print("Error: División por cero")
return None
except TypeError:
print("Error: Los valores deben ser numéricos")
return None
else:
print("División exitosa")
return resultado
finally:
print("Función dividir() finalizada")
print(dividir(10, 2)) # División exitosa, 5.0
print(dividir(10, 0)) # Error, None
print(dividir("a", 2)) # Error, None
9.4. Tipos Comunes de Excepciones
| Excepción | Descripción |
|---|---|
ValueError |
Valor incorrecto |
TypeError |
Tipo de dato incorrecto |
KeyError |
Clave no existe en diccionario |
IndexError |
Índice fuera de rango |
ZeroDivisionError |
División por cero |
FileNotFoundError |
Archivo no encontrado |
AttributeError |
Atributo/método no existe |
ImportError |
Error al importar módulo |
NameError |
Variable no definida |
PermissionError |
Sin permisos |
ConnectionError |
Error de conexión |
TimeoutError |
Tiempo de espera agotado |
Ejemplos de Cada Tipo
# ValueError
int("texto") # No se puede convertir
# TypeError
"texto" + 5 # No se puede sumar string con int
len(123) # int no tiene longitud
# KeyError
d = {"a": 1}
d["b"] # Clave no existe
# IndexError
lista = [1, 2, 3]
lista[10] # Índice fuera de rango
# AttributeError
numero = 5
numero.append(6) # int no tiene método append
# NameError
print(variable_no_definida)
# FileNotFoundError
open("archivo_inexistente.txt")
9.5. Raise - Lanzar Excepciones
Podemos lanzar excepciones manualmente con raise.
def validar_edad(edad):
if edad < 0:
raise ValueError("La edad no puede ser negativa")
if edad > 150:
raise ValueError("La edad no es realista")
return True
try:
validar_edad(-5)
except ValueError as e:
print(f"Error de validación: {e}")
Ejemplos Prácticos
def dividir(a, b):
if b == 0:
raise ZeroDivisionError("El divisor no puede ser cero")
return a / b
def procesar_lista(lista):
if not isinstance(lista, list):
raise TypeError("Se esperaba una lista")
if len(lista) == 0:
raise ValueError("La lista no puede estar vacía")
return sum(lista) / len(lista)
# Uso
try:
resultado = dividir(10, 0)
except ZeroDivisionError as e:
print(e)
try:
media = procesar_lista([])
except ValueError as e:
print(e)
Re-lanzar Excepciones
def procesar_datos(datos):
try:
resultado = datos[0] / datos[1]
return resultado
except Exception as e:
print(f"Error capturado: {e}")
raise # Re-lanza la misma excepción
try:
procesar_datos([10, 0])
except ZeroDivisionError:
print("Error manejado en el nivel superior")
9.6. Excepciones Personalizadas
Podemos crear nuestras propias excepciones.
class MiError(Exception):
"""Excepción personalizada básica."""
pass
class EdadInvalidaError(Exception):
"""Error cuando la edad no es válida."""
def __init__(self, edad, mensaje="Edad no válida"):
self.edad = edad
self.mensaje = mensaje
super().__init__(self.mensaje)
def __str__(self):
return f"{self.mensaje}: {self.edad}"
# Uso
def verificar_edad(edad):
if edad < 0 or edad > 150:
raise EdadInvalidaError(edad)
if edad < 18:
raise EdadInvalidaError(edad, "Debe ser mayor de edad")
return True
try:
verificar_edad(-5)
except EdadInvalidaError as e:
print(e) # Edad no válida: -5
try:
verificar_edad(15)
except EdadInvalidaError as e:
print(e) # Debe ser mayor de edad: 15
Jerarquía de Excepciones
class ErrorAplicacion(Exception):
"""Clase base para errores de la aplicación."""
pass
class ErrorValidacion(ErrorAplicacion):
"""Error en validación de datos."""
pass
class ErrorBaseDatos(ErrorAplicacion):
"""Error en operaciones de base de datos."""
pass
class ErrorConexion(ErrorBaseDatos):
"""Error de conexión a la base de datos."""
pass
# Uso
def conectar_db():
raise ErrorConexion("No se pudo conectar al servidor")
try:
conectar_db()
except ErrorBaseDatos as e:
print(f"Error de BD: {e}")
except ErrorAplicacion as e:
print(f"Error de aplicación: {e}")
9.7. Context Managers
Los context managers garantizan que los recursos se liberen correctamente.
El Statement with
# Sin context manager (puede haber problemas)
archivo = open("datos.txt", "r")
contenido = archivo.read()
archivo.close() # Puede no ejecutarse si hay error
# Con context manager (recomendado)
with open("datos.txt", "r") as archivo:
contenido = archivo.read()
# El archivo se cierra automáticamente
Múltiples Context Managers
with open("entrada.txt", "r") as entrada, open("salida.txt", "w") as salida:
for linea in entrada:
salida.write(linea.upper())
Crear Context Manager con contextlib
from contextlib import contextmanager
@contextmanager
def temporizador():
import time
inicio = time.time()
print("Iniciando...")
yield # Aquí se ejecuta el código del bloque with
fin = time.time()
print(f"Tiempo transcurrido: {fin - inicio:.2f} segundos")
# Uso
with temporizador():
suma = sum(range(1000000))
print(f"Suma: {suma}")
9.8. Buenas Prácticas
1. Ser Específico con las Excepciones
# MAL - Captura todo
try:
# código
pass
except:
print("Error")
# BIEN - Captura específica
try:
# código
pass
except ValueError as e:
print(f"Error de valor: {e}")
except TypeError as e:
print(f"Error de tipo: {e}")
2. No Silenciar Excepciones
# MAL - Ignora el error completamente
try:
resultado = 10 / 0
except:
pass
# BIEN - Al menos registrar el error
try:
resultado = 10 / 0
except ZeroDivisionError as e:
print(f"Advertencia: {e}")
resultado = 0
3. Usar Finally para Limpieza
conexion = None
try:
conexion = abrir_conexion()
# usar conexión
except Exception as e:
print(f"Error: {e}")
finally:
if conexion:
conexion.cerrar()
4. Validar Antes cuando sea Posible
# En lugar de capturar excepción
def obtener_elemento_v1(lista, indice):
try:
return lista[indice]
except IndexError:
return None
# Validar antes (EAFP vs LBYL)
def obtener_elemento_v2(lista, indice):
if 0 <= indice < len(lista):
return lista[indice]
return None
9.9. Ejercicios Prácticos
Ejercicio 1: Calculadora Segura
class CalculadoraError(Exception):
"""Error de la calculadora."""
pass
class DivisionPorCeroError(CalculadoraError):
"""Error de división por cero."""
pass
class OperacionInvalidaError(CalculadoraError):
"""Operación no reconocida."""
pass
def calculadora_segura():
operaciones = {
"+": lambda a, b: a + b,
"-": lambda a, b: a - b,
"*": lambda a, b: a * b,
"/": lambda a, b: a / b if b != 0 else None,
"**": lambda a, b: a ** b
}
while True:
print("\n=== CALCULADORA ===")
entrada = input("Operación (ej: 5 + 3) o 'salir': ").strip()
if entrada.lower() == "salir":
print("¡Hasta luego!")
break
try:
partes = entrada.split()
if len(partes) != 3:
raise OperacionInvalidaError("Formato: número operador número")
num1, op, num2 = partes
num1 = float(num1)
num2 = float(num2)
if op not in operaciones:
raise OperacionInvalidaError(f"Operador '{op}' no válido")
if op == "/" and num2 == 0:
raise DivisionPorCeroError("No se puede dividir por cero")
resultado = operaciones[op](num1, num2)
print(f"Resultado: {resultado}")
except ValueError:
print("Error: Introduce números válidos")
except CalculadoraError as e:
print(f"Error: {e}")
except Exception as e:
print(f"Error inesperado: {e}")
calculadora_segura()
Ejercicio 2: Gestor de Archivos Seguro
import os
import json
class ArchivoError(Exception):
"""Error relacionado con archivos."""
pass
class ArchivoNoEncontradoError(ArchivoError):
"""El archivo no existe."""
pass
class FormatoInvalidoError(ArchivoError):
"""El formato del archivo no es válido."""
pass
def leer_json_seguro(ruta):
"""Lee un archivo JSON de forma segura."""
if not os.path.exists(ruta):
raise ArchivoNoEncontradoError(f"No existe: {ruta}")
try:
with open(ruta, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError as e:
raise FormatoInvalidoError(f"JSON inválido: {e}")
except PermissionError:
raise ArchivoError(f"Sin permisos para leer: {ruta}")
def escribir_json_seguro(datos, ruta, crear_backup=True):
"""Escribe datos en un archivo JSON de forma segura."""
# Crear backup si existe
if crear_backup and os.path.exists(ruta):
backup = ruta + ".backup"
try:
import shutil
shutil.copy(ruta, backup)
except Exception as e:
print(f"Advertencia: No se pudo crear backup: {e}")
try:
with open(ruta, "w", encoding="utf-8") as f:
json.dump(datos, f, indent=2, ensure_ascii=False)
return True
except PermissionError:
raise ArchivoError(f"Sin permisos para escribir: {ruta}")
except Exception as e:
raise ArchivoError(f"Error al escribir: {e}")
# Uso
try:
datos = leer_json_seguro("config.json")
print("Datos cargados:", datos)
except ArchivoNoEncontradoError:
print("Archivo no encontrado, creando uno nuevo...")
datos = {"configuracion": "default"}
escribir_json_seguro(datos, "config.json")
except FormatoInvalidoError as e:
print(f"Error de formato: {e}")
except ArchivoError as e:
print(f"Error de archivo: {e}")
Ejercicio 3: Validador de Formulario
class ValidacionError(Exception):
"""Error de validación."""
def __init__(self, campo, mensaje):
self.campo = campo
self.mensaje = mensaje
super().__init__(f"{campo}: {mensaje}")
class Validador:
@staticmethod
def validar_email(email):
if not email:
raise ValidacionError("email", "El email es obligatorio")
if "@" not in email or "." not in email:
raise ValidacionError("email", "Formato de email inválido")
return True
@staticmethod
def validar_edad(edad):
try:
edad = int(edad)
except (ValueError, TypeError):
raise ValidacionError("edad", "Debe ser un número")
if edad < 0:
raise ValidacionError("edad", "No puede ser negativa")
if edad < 18:
raise ValidacionError("edad", "Debe ser mayor de 18 años")
if edad > 120:
raise ValidacionError("edad", "Edad no realista")
return True
@staticmethod
def validar_telefono(telefono):
numeros = telefono.replace(" ", "").replace("-", "")
if not numeros.isdigit():
raise ValidacionError("teléfono", "Solo se permiten dígitos")
if len(numeros) != 9:
raise ValidacionError("teléfono", "Debe tener 9 dígitos")
return True
@staticmethod
def validar_password(password):
if len(password) < 8:
raise ValidacionError("contraseña", "Mínimo 8 caracteres")
if not any(c.isupper() for c in password):
raise ValidacionError("contraseña", "Debe contener mayúsculas")
if not any(c.isdigit() for c in password):
raise ValidacionError("contraseña", "Debe contener números")
return True
def procesar_formulario(datos):
"""Procesa y valida un formulario."""
errores = []
# Validar cada campo
validaciones = [
(Validador.validar_email, datos.get("email", "")),
(Validador.validar_edad, datos.get("edad", "")),
(Validador.validar_telefono, datos.get("telefono", "")),
(Validador.validar_password, datos.get("password", ""))
]
for validar, valor in validaciones:
try:
validar(valor)
except ValidacionError as e:
errores.append(str(e))
if errores:
print("Errores de validación:")
for error in errores:
print(f" - {error}")
return False
print("Formulario válido ✓")
return True
# Probar
formulario = {
"email": "usuario@ejemplo.com",
"edad": "25",
"telefono": "612 345 678",
"password": "MiPassword123"
}
procesar_formulario(formulario)
formulario_invalido = {
"email": "invalido",
"edad": "quince",
"telefono": "123",
"password": "corta"
}
procesar_formulario(formulario_invalido)
9.10. Resumen
| Concepto | Descripción |
|---|---|
try |
Bloque que puede causar excepciones |
except |
Captura y maneja excepciones |
else |
Se ejecuta si no hay excepciones |
finally |
Se ejecuta siempre |
raise |
Lanza una excepción |
Exception |
Clase base para excepciones |
as e |
Captura la excepción en variable |
try:
# Código que puede fallar
resultado = operacion_riesgosa()
except TipoError as e:
# Manejar error específico
print(f"Error: {e}")
except Exception as e:
# Manejar cualquier otro error
print(f"Error inesperado: {e}")
else:
# Se ejecuta si no hubo errores
print("Éxito")
finally:
# Limpieza, se ejecuta siempre
liberar_recursos()
📅 Fecha de creación: Enero 2026
✍️ Autor: Fran García