Los decoradores son funciones que modifican o amplían el comportamiento de otras funciones, sin necesidad de modificar el código original de estas funciones. En otras palabras, los decoradores permiten envolver una función y agregarle funcionalidad extra. Son una característica poderosa y elegante en Python, y si alguna vez has visto el símbolo @
, seguramente has trabajado con un decorador.
¿Qué es un decorador?
Un decorador es una función que toma otra función como entrada y devuelve una nueva función. En lugar de llamar a la función directamente, la función decorada será ejecutada con el comportamiento adicional que el decorador define.
Ejemplo:
def mi_decorador(func):
def envoltorio():
print("Antes de la función")
func()
print("Después de la función")
return envoltorio
@mi_decorador
def suma():
print("Sumando...")
suma()
En este ejemplo:
mi_decorador
es la función decoradora.suma
es la función original.envoltorio
es la función interna que envuelve y modifica el comportamiento de suma.
La línea @mi_decorador
es una forma de aplicar el decorador sobre la función suma
.
Funciones como objetos:
En Python, las funciones son objetos de primera clase, lo que significa que puedes:
- Asignar una función a una variable.
- Pasar funciones como argumentos a otras funciones.
Esto es importante porque los decoradores se definen como funciones que reciben otras funciones como argumentos y devuelven una nueva función.
Ejemplo:
def di_hola():
print("¡Hola!")
saludo = di_hola
saludo() # Imprime "¡Hola!"
Definiendo decoradores sin @:
Aunque lo más común es usar el símbolo @
, puedes definir y aplicar un decorador
de manera manual:
def mi_decorador(func):
def envoltorio():
print("Función decorada")
return func()
return envoltorio
def suma():
print("Sumando...")
decorada = mi_decorador(suma)
decorada() # Imprime "Función decorada" seguido de "Sumando..."
Esto hace lo mismo que el uso de @mi_decorador
, pero aplicado manualmente.
Decoradores con parámetros:
A veces es útil que un decorador reciba parámetros adicionales. Para hacerlo, necesitamos envolver el decorador de nuevo en una función que acepte estos parámetros. Aquí te mostramos un ejemplo:
def repetir_veces(n):
def decorador(func):
def envoltorio(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return envoltorio
return decorador
@repetir_veces(3)
def saludo():
print("¡Hola!")
saludo() # Imprime "¡Hola!" tres veces
En este caso, repetir_veces
es un decorador que toma el parámetro
n
para repetir la función decorada un número de veces.
Ejemplo práctico de decorador: Logger
Un uso común de los decoradores es registrar (loggear) las actividades de ciertas funciones. A continuación, tenemos un decorador log
que escribe el resultado de una función en un archivo:
import datetime
def log(func):
def envoltorio(*args, **kwargs):
resultado = func(*args, **kwargs)
with open("log.txt", "a") as f:
f.write(f"{datetime.datetime.now()} - {func.__name__} fue llamada y retornó: {resultado}\n")
return resultado
return envoltorio
@log
def suma(a, b):
return a + b
print(suma(3, 4)) # Guarda en el archivo log.txt la información sobre esta ejecución
Ejemplo práctico de decorador: Autenticación
En aplicaciones web, especialmente en frameworks como Flask, los decoradores son muy útiles para gestionar la autenticación. Aquí tienes un ejemplo simplificado de un decorador que verifica si un usuario está autenticado antes de permitirle acceder a una función:
def requiere_autenticacion(func):
def envoltorio(*args, **kwargs):
if not autenticado:
raise PermissionError("No estás autenticado")
return func(*args, **kwargs)
return envoltorio
autenticado = False # Cambia a True para permitir el acceso
@requiere_autenticacion
def acceder_recurso():
print("Recurso accedido")
# Intento de acceso
try:
acceder_recurso()
except PermissionError as e:
print(e) # Imprime "No estás autenticado"