- ¿Qué es un decorador?
- Decoradores: funciones que modifican funciones
- Uso de decoradores
- Decoradores con parámetros
- Decorador que filtra datos
- Resumen
En programación, a menudo necesitamos agregar funcionalidad a funciones o clases existentes sin modificar su código original.
Ejemplos comunes incluyen:
- Registro de logs (guardar información de ejecución).
- Validación de datos (verificar entradas antes de ejecutar).
- Medición de tiempo de ejecución (ver cuánto tarda una función).
Python tiene una herramienta poderosa para esto: los decoradores. Veamos cómo funcionan.
¿Qué es un decorador?
Un decorador es una función que envuelve otra función para modificar su comportamiento sin cambiar su código.
Primero, recordemos cómo funcionan los cierres (closures):
def outer(arg1):
def inner(arg2):
return arg1 + arg2
return inner
sumar_dos = outer(2)
sumar_dos(1) # 3
sumar_dos(3) # 5
sumar_dos(5) # 7
La función outer() recibe un número y devuelve otra función que suma ese número a otro. sumar_dos recuerda el valor 2 gracias al closure.
Podemos reescribirlo con lambdas:
def outer(arg1):
inner = lambda arg2: arg1 + arg2
return inner
En Python, las funciones son objetos, por lo que pueden manipularse como datos.
Decoradores: funciones que modifican funciones
Podemos aplicar este concepto a funciones:
def outer(func):
def inner(arg):
print('Función:', func)
print('Argumento:', arg)
return func(arg)
return inner
def cuadrado(x):
return x ** 2
envoltura = outer(cuadrado)
envoltura(1) # 1
# => Función: <function cuadrado>
# => Argumento: 1
envoltura(2) # 4
# => Función: <function cuadrado>
# => Argumento: 2
outer() recibe una función y devuelve una nueva función inner() que la ejecuta y agrega mensajes adicionales. No modificamos cuadrado(), solo la envolvemos. Este es el concepto de decoradores.
Uso de decoradores
Supongamos que tenemos esta función para sumar números:
def sumar(*nums):
resultado = 0
for num in nums:
resultado += num
return resultado
Creamos un decorador para depurar (debugging):
def debug_decorator(func):
def wrapper(*args, **kwargs):
print("Args:", args, kwargs)
resultado = func(*args, **kwargs)
print("Resultado:", resultado)
return resultado
return wrapper
wrapper() no se llama directamente, sino que actúa como una función interna dentro del decorador
Aplicamos el decorador:
sumar = debug_decorator(sumar)
sumar(1, 2, 3) # 6
# => Args: (1, 2, 3) {}
# => Resultado: 6
Ahora, cada vez que llamemos sumar(), se ejecuta con mensajes de depuración.
Sintaxis con @decorador
Python permite aplicar decoradores de manera más elegante:
@debug_decorator
def sumar(*nums):
resultado = 0
for num in nums:
resultado += num
return resultado
sumar(1, 2) # 3
# => Args: (1, 2) {}
# => Resultado: 3
@debug_decorator reemplaza la función original por la versión decorada.
Podemos encadenar decoradores para agregar múltiples funcionalidades:
@debug_decorator
@time_decorator
def sumar(*nums):
resultado = 0
for num in nums:
resultado += num
return resultado
En este caso, la función sumar es primero transformada por time_decorator y luego por debug_decorator.
Es decir, cuando se llama a sumar, primero se ejecuta debug_decorator y luego time_decorator
Decoradores con parámetros
¿Qué pasa si queremos modificar el comportamiento del decorador según una configuración? Podemos hacer un decorador con parámetros:
def decorador_con_params(param1, param2):
def decorador_real(func):
def wrapper(*args, **kwargs):
print(f"Parámetros del decorador: {param1}, {param2}")
resultado = func(*args, **kwargs)
return resultado
return wrapper
return decorador_real
Aplicamos el decorador con valores personalizados:
@decorador_con_params("Info", 10)
def funcion_prueba():
print("Función ejecutada")
funcion_prueba()
# => Parámetros del decorador: Info, 10
# => Función ejecutada
@decorador_con_params("Info", 10) personaliza el decorador.
Decorador que filtra datos
Creamos un decorador que filtra números antes de sumarlos:
def filtrar_por(filtro):
def decorador(func):
def wrapper(*args):
args = filtro(args)
print('Argumentos filtrados:', args)
return func(*args)
return wrapper
return decorador
Definimos una función que filtra los números impares:
def impares(numeros):
return [num for num in numeros if num % 2 == 1]
@filtrar_por(impares)
def sumar(*nums):
return sum(nums)
sumar(3, 4, 5, 6) # 8
# => Argumentos filtrados: [3, 5]
Solo se suman los números impares.
Podemos hacer lo mismo con un filtro de límite:
def menor_que(maximo):
def filtro(numeros):
return [num for num in numeros if num < maximo]
return filtro
@filtrar_por(menor_que(5))
def sumar(*nums):
return sum(nums)
sumar(3, 4, 5, 6) # 7
# => Argumentos filtrados: [3, 4]
Solo suma los números menores a 5.
Resumen
- Los decoradores permiten extender funciones sin modificar su código.
- Son ampliamente usados en Python para:
- Registro de logs (
@logging) - Medición de tiempo (
@timeit) - Autenticación en APIs (
@auth_required) - Caché de resultados (
@lru_cache)
- Registro de logs (
Son una herramienta poderosa, pero deben usarse con cuidado para no afectar el rendimiento ni la claridad del código.
Para acceder completo a curso necesitas un plan básico
El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.