Lecciones del módulo (4/4)
Decoradores: funciones que modifican funciones
Un decorador (decorator) es una función que toma otra función y devuelve una versión "decorada" de ella. Es una forma potente de añadir comportamiento (registro, medición de tiempo, almacenamiento en caché, reintentos, validación) sin modificar el cuerpo de la función original.
Funciones como valores
En Python, las funciones son objetos: puedes pasarlas como argumentos, devolverlas y asignarlas.
def saluta():
print("ciao")
f = saluta # NIENTE parentesi: assegno la funzione
f() # 'ciao'Un decorador mínimo
def log(func):
def wrapper(*args, **kwargs):
print(f"chiamo {func.__name__}")
risultato = func(*args, **kwargs)
print(f"ho finito {func.__name__}")
return risultato
return wrapper
def somma(a, b):
return a + b
somma = log(somma) # ora somma è "wrapper"
somma(2, 3)
# chiamo somma
# ho finito somma
# 5Sintaxis de @decorator
La asignación somma = log(somma) se escribe de forma más elegante con @:
@log
def somma(a, b):
return a + bEs exactamente lo mismo. @log encima de la definición equivale a: "tan pronto como se defina esta función, reemplázala con log(función)".
*args, **kwargs: aceptar cualquier firma
def wrapper(*args, **kwargs):
return func(*args, **kwargs)*args recopila argumentos posicionales, **kwargs argumentos de palabra clave. Te permiten escribir envoltorios (wrappers) que funcionan con cualquier función.
functools.wraps: conservar metadatos
Sin wraps, la función decorada pierde __name__, __doc__ y los hints:
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperEjemplo: @timeit
from functools import wraps
import time
def timeit(func):
@wraps(func)
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
risultato = func(*args, **kwargs)
dt = time.perf_counter() - t0
print(f"{func.__name__} ha impiegato {dt*1000:.2f} ms")
return risultato
return wrapper
@timeit
def somma_grande():
return sum(range(10**6))
somma_grande()Decoradores de la biblioteca estándar (stdlib)
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2: return n
return fib(n - 1) + fib(n - 2)
fib(100) # istantaneo grazie alla cache@lru_cache— memoriza los resultados de una función pura.@property— expone un método como si fuera un atributo (visto en el módulo OOP).@staticmethod,@classmethod— cambian la forma en que se pasaself.
Decoradores con argumentos
Los decoradores también pueden aceptar argumentos. Para implementar esto, debes escribir tres niveles de funciones anidadas: la función más externa recibe los argumentos del decorador, la función intermedia recibe la función decorada, y la función más interna (wrapper) recibe los argumentos reales pasados a la función decorada durante las llamadas.
Pruébalo tú
Escribe un decorador `double_result` que llame a la función decorada y devuelva su resultado * 2. Aplícalo a una función `f(x)` que devuelva x + 1. Evalúa `f(10)`.
Mostrar pista
El wrapper llama a func y devuelve el resultado multiplicado por 2.
Solución disponible después de 3 intentos
Ejercicio de repaso
Usa functools.lru_cache para acelerar una función recursiva fib(n). Calcula fib(30) y asígnalo a `r`. Evalúa `r`.
Mostrar pista
@lru_cache(maxsize=None) encima del def.
Solución disponible después de 3 intentos
Desafío adicional
Define un decorador `double_result` que duplique (multiplique por 2) el valor de retorno de la función decorada. Aplica el decorador a la función `def sum_nums(a, b): return a + b`. Finalmente, evalúa `sum_nums(3, 4)`.
Mostrar pista
El wrapper llama a func(*args, **kwargs) y multiplica el resultado por 2.
Solución disponible después de 3 intentos