1 usuario conectado

Funciones. Decoradores en Python. Ejercicios

Comparte esto

  Ejercicio 1: Medir el Tiempo de Ejecución

  1. Crea un decorador llamado medir_tiempo que tome una función como argumento.
  2. Este decorador debería registrar el tiempo de inicio antes de ejecutar la función y el tiempo de finalización después de que la función termine.
  3. Imprime la diferencia entre el tiempo de finalización y el tiempo de inicio para mostrar el tiempo total de ejecución de la función decorada.
  4. Aplica este decorador a una función que realice alguna tarea que tome un poco de tiempo (por ejemplo, un bucle que itere muchas veces o una llamada a time.sleep()).

  Ejercicio 2: Registrar Llamadas a Función

  1. Crea un decorador llamado registrar_llamada que tome una función como argumento.
  2. Este decorador debería imprimir un mensaje cada vez que la función decorada sea llamada, incluyendo el nombre de la función.
  3. Aplica este decorador a varias funciones diferentes y llámalas para verificar que el mensaje de registro se imprima correctamente.

  Ejercicio 3: Validar Argumentos

  1. Crea un decorador llamado validar_argumento que tome un tipo esperado como argumento (por ejemplo, int, str).
  2. Este decorador debería tomar una función como argumento.
  3. Dentro del decorador, verifica el tipo del argumento de la función decorada. Si el tipo no coincide con el tipo esperado, levanta una excepción TypeError con un mensaje informativo.
  4. Aplica este decorador a una función que espera un argumento de un tipo específico y pruébala llamándola con argumentos del tipo correcto e incorrecto.

  Ejercicio 4: Memorización (Caching Simple)

  1. Crea un decorador llamado memoizar que tome una función como argumento.
  2. Este decorador debería almacenar los resultados de la función en un diccionario.
  3. Antes de ejecutar la función, verifica si los argumentos con los que se llama ya existen como clave en el diccionario de resultados.
  4. Si existen, devuelve el resultado almacenado en lugar de ejecutar la función nuevamente.
  5. Si no existen, ejecuta la función, almacena el resultado en el diccionario usando los argumentos como clave y luego devuelve el resultado.
  6. Aplica este decorador a una función que sea computacionalmente costosa y llámala varias veces con los mismos argumentos para observar el efecto de la memorización.

  Ejercicio 5: Retry con Límite

  1. Crea un decorador llamado intentar_de_nuevo que tome un número máximo de intentos como argumento.
  2. Este decorador debería tomar una función que pueda fallar (levantar una excepción) como argumento.
  3. Dentro del decorador, intenta ejecutar la función. Si levanta una excepción, atrapa la excepción y espera un breve período de tiempo antes de intentar ejecutar la función nuevamente, hasta alcanzar el número máximo de intentos.
  4. Si la función aún falla después del número máximo de intentos, levanta la última excepción.
  5. Aplica este decorador a una función que simule una operación que puede fallar aleatoriamente.


Mostrar/Ocultar ejercicios resueltos


  Solución al Ejercicio 1: Medir el Tiempo de Ejecución

Python
 
import time

def medir_tiempo(func):
    def wrapper(*args, **kwargs):
        inicio = time.time()
        resultado = func(*args, **kwargs)
        fin = time.time()
        print(f"La función '{func.__name__}' tardó {fin - inicio:.4f} segundos en ejecutarse.")
        return resultado
    return wrapper

@medir_tiempo
def tarea_larga():
    time.sleep(2)
    print("Tarea larga completada.")

@medir_tiempo
def calcular_suma(n):
    suma = 0
    for i in range(n):
        suma += i
    return suma

tarea_larga()
resultado_suma = calcular_suma(1000000)
print(f"Resultado de la suma: {resultado_suma}")

  Solución al Ejercicio 2: Registrar Llamadas a Función

Python
 
def registrar_llamada(func):
    def wrapper(*args, **kwargs):
        print(f"Llamando a la función: '{func.__name__}' con argumentos: {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@registrar_llamada
def saludar(nombre):
    return f"Hola, {nombre}!"

@registrar_llamada
def sumar(a, b):
    return a + b

saludo = saludar("Mundo")
resultado_suma = sumar(5, 3)
print(saludo)
print(resultado_suma)

  Solución al Ejercicio 3: Validar Argumentos

Python
 
def validar_argumento(tipo_esperado):
    def decorador(func):
        def wrapper(*args, **kwargs):
            for arg in args:
                if not isinstance(arg, tipo_esperado):
                    raise TypeError(f"El argumento debe ser de tipo {tipo_esperado}, pero se recibió {type(arg)}")
            for kwarg in kwargs.values():
                if not isinstance(kwarg, tipo_esperado):
                    raise TypeError(f"El argumento '{kwarg}' debe ser de tipo {tipo_esperado}, pero se recibió {type(kwarg)}")
            return func(*args, **kwargs)
        return wrapper
    return decorador

@validar_argumento(int)
def multiplicar_por_dos(numero):
    return numero * 2

@validar_argumento(str)
def concatenar(texto1, texto2=""):
    return texto1 + texto2

resultado_multiplicacion = multiplicar_por_dos(5)
print(resultado_multiplicacion)

try:
    multiplicar_por_dos("hola")
except TypeError as e:
    print(f"Error: {e}")

resultado_concatenacion = concatenar("Hola", " Mundo")
print(resultado_concatenacion)

try:
    concatenar(123)
except TypeError as e:
    print(f"Error: {e}")

  Solución al Ejercicio 4: Memorización (Caching Simple)

Python
 
def memoizar(func):
    cache = {}
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoizar
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))
print(fibonacci(10)) # El resultado se obtiene del caché
print(fibonacci(20))

  Solución al Ejercicio 5: Retry con Límite

Python
 
import time
import random

def intentar_de_nuevo(max_intentos):
    def decorador(func):
        def wrapper(*args, **kwargs):
            intentos = 0
            while intentos < max_intentos:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    intentos += 1
                    print(f"Error al llamar a '{func.__name__}': {e}, intentando de nuevo ({intentos}/{max_intentos})...")
                    time.sleep(random.uniform(0.1, 0.5)) # Espera un poco antes de reintentar
            raise Exception(f"Falló después de {max_intentos} intentos.")
        return wrapper
    return decorador

@intentar_de_nuevo(3)
def operacion_fallida_aleatoriamente():
    if random.random() < 0.7:
        raise IOError("¡La operación falló!")
    return "Operación exitosa."

try:
    resultado = operacion_fallida_aleatoriamente()
    print(f"Resultado: {resultado}")
except Exception as e:
    print(f"Error final: {e}")