Lección 5 · 5 de 20
Decoradores · Python
Añada registro y control de permisos a funciones de Python sin tocarlas: decoradores, decoradores con argumentos y functools.wraps.

Un decorador es una función que recibe una función y devuelve otra que la envuelve. log_calls(func) define wrapper, que llama a la original, escribe su nombre y su resultado, y lo devuelve. Es una clausura: wrapper recuerda func.
Escribir @log_calls encima de una definición equivale a refund = log_calls(refund): a partir de ahí, el nombre refund apunta al envoltorio. @functools.wraps(func) copia en el envoltorio el nombre y la documentación de la original; sin él, refund.__name__ valdría "wrapper".
require_role("admin") es un decorador con argumentos: es una función que devuelve el decorador de verdad. Por eso hay tres niveles: require_role recibe el rol, decorator recibe la función y wrapper recibe los argumentos de cada llamada. El envoltorio mira user["roles"] y, si falta el rol, ni siquiera llama a la función.
Los decoradores se apilan: se aplica primero el de abajo. refund queda protegida por require_role y todo ello envuelto por log_calls, así que el registro anota también las llamadas denegadas, como se ve con Luis.
Así se separa lo que hace la función (calcular un reembolso) de lo que se repite en muchas (registrar, comprobar permisos, medir tiempos, reintentar).
Código · main.py
import functoolsdef log_calls(func): @functools.wraps(func) # keep the original name and docstring def wrapper(*args): result = func(*args) print(f"log: {func.__name__} -> {result}") return result return wrapperdef require_role(role): # a decorator with arguments def decorator(func): @functools.wraps(func) def wrapper(user, *args): if role not in user["roles"]: return f"denied: {user['name']} is not {role}" return func(user, *args) return wrapper return decorator@log_calls # decorators stack: bottom one first@require_role("admin")def refund(user, amount): return f"{user['name']} refunded {amount:.2f} EUR"ana = {"name": "Ana", "roles": ["admin", "sales"]}luis = {"name": "Luis", "roles": ["sales"]}print(refund(ana, 30))print(refund(luis, 30))print("Name kept:", refund.__name__)Salida · python3 main.py
log: refund -> Ana refunded 30.00 EUR Ana refunded 30.00 EUR log: refund -> denied: Luis is not admin denied: Luis is not admin Name kept: refund
Para recordar
@decoencima dedef fequivale af = deco(f).- El envoltorio suele aceptar
*argsy**kwargsy devolver el resultado. - Use siempre
@functools.wraps(func)en el envoltorio. - Un decorador con argumentos es una función que devuelve un decorador.
- Varios decoradores se aplican de abajo arriba.
Ejercicio
Escriba un decorador @uppercase que convierta en mayúsculas la cadena que devuelve la función y aplíquelo a refund encima de los otros dos.