En este capítulo, exploramos cómo elevar un simple gestor de tareas en Python a un nivel más profesional mediante una refactorización modular. Tomaremos el proyecto base de capítulos anteriores –un script que maneja listas de tareas con operaciones como agregar, eliminar y listar– y lo transformaremos en una estructura de paquete con módulos separados. Incorporaremos decoradores simples como @timer para medir tiempos de ejecución y @retry para reintentos básicos en caso de errores. Enfocaremos en funciones reutilizables y validaciones robustas, todo sin recurrir a clases u OOP, para mantener la simplicidad mientras ganamos en mantenibilidad y escalabilidad. Imagina esto como reorganizar un armario desordenado: cada módulo es un cajón dedicado, los decoradores son etiquetas inteligentes que agregan funcionalidad sin ensuciar el contenido principal.
Construyendo una Estructura Modular Sólida
Comencemos por lo fundamental: los módulos en Python son archivos .py que contienen definiciones reutilizables, como funciones o variables. Al agruparlos en una estructura de paquete simple –básicamente una carpeta con un archivo __init__.py vacío– creamos un proyecto organizado y fácil de importar. Piensa en un módulo como un toolbox: cada uno guarda herramientas específicas, y el paquete es el estante que las une.
Para este refactor, crearemos una carpeta llamada gestor_tareas con subarchivos. Esto permite separar lógica: un módulo para operaciones de tareas, otro para utilidades como decoradores, y un script principal para ejecutar todo.
Reglas de oro para este curso:
- Explicaciones profundas, paso a paso y con analogías cotidianas cuando ayuden.
- Cada concepto nuevo se explica completamente antes de pasar al siguiente.
- También al primer ejemplo indicar al como ejecutarlo ejemplo
python nombre.py - Cada bloque de código lleva comentarios explicativos claros.
- El lector debe terminar el capítulo sintiendo que realmente domina el tema, no solo que “lo ha visto”.
Crea la estructura así:
- Carpeta:
gestor_tareas/__init__.py(vacío, para marcar como paquete)tareas.py(lógica principal de tareas)utilidades.py(decoradores y funciones reutilizables)
- Archivo principal fuera:
main.py
Ahora, veamos un ejemplo inicial. En utilidades.py, define una función simple reutilizable para validar entradas. Guarda esto y ejecútalo con python utilidades.py (aunque en producción lo importarás, este test verifica que funcione standalone).
# utilidades.py
def validar_entrada(texto, longitud_max=50):
"""Valida que el texto no esté vacío y no exceda la longitud máxima."""
if not texto.strip():
raise ValueError("La entrada no puede estar vacía.")
if len(texto) > longitud_max:
raise ValueError(f"La entrada excede {longitud_max} caracteres.")
return texto.strip() # Devuelve el texto limpio
# Test simple (ejecuta con python utilidades.py)
if __name__ == "__main__":
try:
print(validar_entrada("Tarea válida"))
print(validar_entrada(" ")) # Debería fallar
except ValueError as e:
print(f"Error: {e}")
PythonEjecuta python utilidades.py en tu terminal. Verás una salida exitosa para la primera llamada y un error controlado para la segunda. Esta validación es reutilizable: la llamaremos en otros módulos para asegurar datos limpios, evitando errores downstream como en un juego de dominó donde una pieza caída arruina todo.
Dominando Decoradores: @timer para Medir Eficiencia
Un decorador en Python es una función que envuelve otra para agregar comportamiento extra sin modificar su código original. Es como poner un marco alrededor de una pintura: realza sin alterar la obra. Explico paso a paso: un decorador toma una función como argumento, define una función interna (wrapper) que ejecuta la original más algo adicional, y retorna ese wrapper.
Creemos @timer, que mide el tiempo de ejecución. En utilidades.py, agrégalo así. Primero, explica completamente: importamos time para cronometrar. El wrapper calcula el inicio, llama a la función original, calcula el fin y imprime la diferencia.
# utilidades.py (agrega al final del archivo anterior)
import time
def timer(func):
"""Decorador que mide el tiempo de ejecución de una función."""
def wrapper(*args, **kwargs):
inicio = time.time() # Marca el tiempo de inicio
resultado = func(*args, **kwargs) # Ejecuta la función original
fin = time.time() # Marca el tiempo de fin
print(f"Ejecución de {func.__name__} tomó {fin - inicio:.4f} segundos.")
return resultado # Retorna el resultado original
return wrapper
PythonPara usarlo, decora cualquier función. Por analogía, imagina @timer como un cronómetro en una carrera: no cambia cómo corres, solo registra el tiempo. Repito para claridad: el decorador no altera la lógica interna, solo añade medición –ideal para optimizar código lento.
Implementando @retry para Robustez en Errores
Ahora, @retry básico: este decorador reintenta una función un número fijo de veces si falla. Es útil para operaciones propensas a errores temporales, como accesos a archivos. Paso a paso: definimos el decorador con un parámetro para reintentos (por defecto 3). El wrapper intenta ejecutar la función; si hay excepción, reintenta hasta el límite.
En utilidades.py:
# utilidades.py (agrega al final)
def retry(max_intentos=3):
"""Decorador que reintenta una función en caso de error."""
def decorador(func):
def wrapper(*args, **kwargs):
intentos = 0
while intentos < max_intentos:
try:
return func(*args, **kwargs) # Intenta ejecutar
except Exception as e: # Captura cualquier excepción
intentos += 1
print(f"Intento {intentos} fallido: {e}. Reintentando...")
raise Exception(f"Falló después de {max_intentos} intentos.") # Falla final
return wrapper
return decorador # Retorna el decorador interno
PythonEsto es un decorador con parámetros: la función externa toma max_intentos, y retorna el decorador real. Analogía: como un seguro con deducible –reintenta hasta agotar, luego falla. Úsalo en funciones que podrían fallar intermitentemente, como lecturas de archivos en redes inestables.
Refactorizando el Gestor de Tareas con Módulos y Funciones Reutilizables
Integramos todo en tareas.py. Asumimos un gestor base: una lista global de tareas. Separamos funciones como agregar_tarea, listar_tareas, etc., aplicando decoradores y validaciones. Estas funciones son reutilizables: se importan fácilmente.
En tareas.py:
# tareas.py
from utilidades import validar_entrada, timer, retry
tareas = [] # Lista global de tareas (simple, sin OOP)
@timer
@retry(max_intentos=2) # Aplica ambos decoradores: retry primero, luego timer
def agregar_tarea(nombre):
"""Agrega una tarea validada a la lista."""
nombre_valido = validar_entrada(nombre) # Usa función reutilizable
tareas.append(nombre_valido)
print(f"Tarea '{nombre_valido}' agregada.")
@timer
def listar_tareas():
"""Lista todas las tareas."""
if not tareas:
print("No hay tareas.")
for idx, tarea in enumerate(tareas, 1):
print(f"{idx}. {tarea}")
# Otras funciones como eliminar_tarea podrían agregarse similarmente
PythonAquí, agregar_tarea usa validación reutilizable y decoradores apilados (el orden importa: @retry envuelve primero). Para ejecutar, crea main.py fuera de la carpeta:
# main.py
from gestor_tareas.tareas import agregar_tarea, listar_tareas
agregar_tarea("Comprar leche")
agregar_tarea(" ") # Debería fallar con validación
listar_tareas()
PythonEjecuta python main.py. Verás medición de tiempo, reintentos si aplica, y validaciones en acción. Repito: esta modularidad hace el código escalable –agrega módulos sin tocar el principal.
Validaciones Avanzadas y Mejores Prácticas
Las validaciones no son opcionales; son guardianes que previenen bugs. En nuestro caso, validar_entrada chequea vacío y longitud. Expándela para más: agrega chequeos de tipo o patrones. Por ejemplo, para tareas numéricas. Analogía: como un portero en una fiesta –verifica invitaciones antes de entrar.
Asegúrate de que cada función reutilizable sea independiente: prueba individualmente. Con módulos, importa solo lo necesario, reduciendo acoplamiento. Al final, dominarás esto: refactorizarás cualquier script en un paquete modular con decoradores, ganando robustez sin complejidad.
Resumen del capítulo
- Estructura modular: Creamos un paquete simple con módulos para separar lógica, mejorando la organización y reutilización.
- Decoradores simples: Implementamos @timer para medir tiempos y @retry para manejar errores con reintentos, explicados paso a paso con wrappers.
- Funciones reutilizables: Desarrollamos utilidades como
validar_entradaque se importan y usan en múltiples partes del proyecto. - Validaciones integradas: Añadimos chequeos robustos para entradas, previniendo errores y asegurando datos limpios.
- Proyecto refactorizado: Transformamos el gestor de tareas en un sistema modular, con ejecución vía
main.py, aplicando todos los conceptos para un código optimizado y mantenible.