DMarlon Studio

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.

Python · Decoradores en DMarlon Studio: el código del ejemplo y su salida en la terminal

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

  • @deco encima de def f equivale a f = deco(f).
  • El envoltorio suele aceptar *args y **kwargs y 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.