Fábrica de decoradores

La función fabrica_decoradores se usa como decorador, pero es en realidad una fábrica de decoradores. Si el argumento fun es una función, tal como suecede cuando se usa @fabrica_decoradores, se devuelve fun ya decorada.

En cambio, si fun no es una función, como cuando se usa @fabrica_decoradores(mensaje='Procesando datos con'), se devuelve el decorador.

import time

def fabrica_decoradores(fun=None, mensaje="Ejecutando función"):
    def decorador(fun):
        def wrapper(*args, **kwargs):
            print(f"{mensaje}: {fun.__name__}")
            inicio = time.perf_counter()
            resultado = fun(*args, **kwargs)
            fin = time.perf_counter()
            print(f"{fun.__name__} terminó en {fin - inicio:.3f} s")
            return resultado
        return wrapper

    # Si fun es callable (i.e., algo que se puede llamar, una funcion), el decorador
    # se llamó sin argumentos. Por lo tanto, aplicamos el decorador manualmente.
    if callable(fun):
        return decorador(fun)

    # Caso contrario, se usó con paréntesis y solo se devuelve el decorador.
    # Python luego lo aplica automáticamente.
    return decorador

Ejemplos

# Ejemplo de uso SIN argumentos
@fabrica_decoradores
def tarea_simple():
    time.sleep(0.2)
    print("Hecho.")

tarea_simple()
Ejecutando función: tarea_simple
Hecho.
tarea_simple terminó en 0.210 s
# Ejemplo de uso CON argumentos
@fabrica_decoradores(mensaje="Procesando datos con")
def tarea_compleja():
    time.sleep(2.2)
    print("Listo.")

tarea_compleja()
Procesando datos con: tarea_compleja
Listo.
tarea_compleja terminó en 2.216 s