Saltar al contenido principal
eLearner.app
Módulo 9 · Lección 4 de 436/36 en el curso~14 min
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.

Python
def saluta():
    print("ciao")

f = saluta   # NIENTE parentesi: assegno la funzione
f()          # 'ciao'

Un decorador mínimo

Python
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
# 5

Sintaxis de @decorator

La asignación somma = log(somma) se escribe de forma más elegante con @:

Python
@log
def somma(a, b):
    return a + b

Es 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

Python
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:

Python
from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ...
        return func(*args, **kwargs)
    return wrapper

Ejemplo: @timeit

Python
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)

Python
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 pasa self.

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ú

Ejercicio#python.m9.l4.e1
Intentos: 0Cargando...

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)`.

Cargando editor...
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

Ejercicio#python.m9.l4.e2
Intentos: 0Cargando...

Usa functools.lru_cache para acelerar una función recursiva fib(n). Calcula fib(30) y asígnalo a `r`. Evalúa `r`.

Cargando editor...
Mostrar pista

@lru_cache(maxsize=None) encima del def.

Solución disponible después de 3 intentos

Desafío adicional

Ejercicio#python.m9.l4.e3
Intentos: 0Cargando...

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)`.

Cargando editor...
Mostrar pista

El wrapper llama a func(*args, **kwargs) y multiplica el resultado por 2.

Solución disponible después de 3 intentos