Python es un lenguaje de programación que destaca por su simplicidad, flexibilidad y con el que es fácil escribir código limpio. Siendo los decoradores una de las características del lenguaje que más ayudan a esto. Los decoradores permiten extender el comportamiento de las funciones y métodos de una manera elegante, facilitando la reutilización del código. En esta entrada, se explorará qué son los decoradores en Python, cómo se pueden crear decoradores personalizados y las ventajas de su uso, incluyendo ejemplos con los que ilustrar su utilidad.
Tabla de contenidos
En Python, un decorador es una función que recibe otra función como argumento, modifica su comportamiento y devuelve una nueva función. Permitiendo de este modo inyectar código antes o después de llamar a la función original. Los decoradores se utilizan habitualmente para realizar tareas de autenticación, validación, modificación del flujo de control, depuración o administración de recursos, entre otras.
La sintaxis básica para aplicar un decorador a una función es utilizando el símbolo @ seguido del nombre del decorador justo antes de la definición de la función. En el siguiente código se muestra un ejemplo básico:
def mi_decorador(func):
def nueva_funcion():
print("Algo se ejecuta antes de la función principal")
func()
print("Algo se ejecuta después de la función principal")
return nueva_funcion
@mi_decorador
def funcion_principal():
print("Esta es la función principal")
funcion_principal() En este caso, al llamar a funcion_principal() se mostrará por pantalla tanto los mensajes del decorador como los de la función. El resultado de ejecutar el código será:
Algo se ejecuta antes de la función principal
Esta es la función principal
Algo se ejecuta después de la función principal
Básicamente lo que se ha implementado en este ejemplo es:
mi_decorador es una función que toma una función como argumento (func). En su código, se define y retorna una nueva función (nueva_funcion) basada en la función que se recibe como argumento.@mi_decorador antes de la definición de funcion_principal indica que el decorador se debe aplicar a la función. Esto hace que en el código funcion_principal sea realmente nueva_funcion.funcion_principal(), en realidad se está llamando a nueva_funcion. La cual primero imprime el primer mensaje, llama a la función original func() y finalmente imprime otro mensaje.Los decoradores se pueden anidar, esto es, se puede aplicar más de un decorador a la misma función. Aplicándose los decoradores de manera envolvente, es decir, el último decorador aplicado será el primero en ejecutarse. Esto es lo que se muestra en el siguiente código de ejemplo.
def decorador_1(func):
def nueva_funcion_1():
print("Decorador 1 antes")
func()
print("Decorador 1 después")
return nueva_funcion_1
def decorador_2(func):
def nueva_funcion_2():
print("Decorador 2 antes")
func()
print("Decorador 2 después")
return nueva_funcion_2
@decorador_1
@decorador_2
def funcion_principal():
print("Función principal")
funcion_principal() El resultado será:
Decorador 1 antes
Decorador 2 antes
Función principal
Decorador 2 después
Decorador 1 después
Como se puede ver en la salida, primero se aplica decorador_2 , el último decorador, y sobre la función que devuelve este el decorador_1.
El uso de decoradores en Python ofrece múltiples ventajas, especialmente cuando se desea escribir código limpio y reutilizable. A continuación, se detallan algunas de las principales ventajas:
@decorador encima de una función, se sabe que se está aplicando una transformación o añadido a esa función, lo cual mejora la legibilidad del código.Una vez que se comprende que son los decoradores y algunas de sus ventajas, se pueden ver algunos casos prácticos de estos a modo de ejemplo.
Uno de los usos más habituales de los decoradores es medir el tiempo de ejecución de una función. Algo especialmente útil durante la optimización de código, donde es necesario identificar qué partes del código están tardando más tiempo en ejecutarse.
import time
def medir_tiempo(func):
def wrapper(*args, **kwargs):
inicio = time.time() # Capturar el tiempo de inicio
resultado = func(*args, **kwargs) # Ejecutar la función original
fin = time.time() # Capturar el tiempo al finalizar
print(f"Tiempo de ejecución: {fin - inicio:.4f} segundos")
return resultado
return wrapper
@medir_tiempo
def funcion_lenta():
time.sleep(2) # Simular una función que tarda 2 segundos en ejecutarse
print("Función lenta ejecutada")
funcion_lenta() En el decorador medir_tiempo la función wrapper toma cualquier número de argumentos (*args, **kwargs), lo que permite decorar funciones con diferentes firmas. Dentro del decorador se usa time.time() para obtener el tiempo antes y después de la ejecución de la función. La diferencia entre ambos valores, el tiempo transcurrido, se imprime en segundos con cuatro decimales.
El resultado al ejecutar funcion_lenta() será algo como:
Función lenta ejecutada
Tiempo de ejecución: 2.0055 segundos
Otro caso de uso especialmente útil de los decoradores es la creación de un registro de las llamadas a una función. Lo que permite realizar una auditoría o depuración de los programas. A continuación, se muestra un ejemplo de este caso.
def registrar_accion(func):
def wrapper(*args, **kwargs):
print(f"Llamando a {func.__name__} con {args} y {kwargs}")
resultado = func(*args, **kwargs)
print(f"{func.__name__} terminó de ejecutarse")
return resultado
return wrapper
@registrar_accion
def sumar(a, b):
return a + b
@registrar_accion
def saludar(nombre):
print(f"Hola, {nombre}!")
suma = sumar(3, 5)
saludar("Juan") El decorador registrar_accion envuelve la función original e imprime un mensaje antes y después de su ejecución, mostrando el nombre de la función y sus argumentos. Unos registros extremadamente útiles para monitorear y depurar aplicaciones.
La salida del código será:
Llamando a sumar con (3, 5) y {}
sumar terminó de ejecutarse
Llamando a saludar con ('Juan',) y {}
Hola, Juan!
saludar terminó de ejecutarse En aplicaciones más complejas, a menudo es necesario controlar el acceso a ciertas funciones o métodos, especialmente en aplicaciones web o sistemas con múltiples usuarios. Verificar si un usuario tiene los permisos adecuados antes de permitir la ejecución de una función es algo que se puede hacer fácilmente con decoradores.
def verificar_acceso(func):
def wrapper(usuario, *args, **kwargs):
if usuario != "admin":
print("Acceso denegado")
return
return func(usuario, *args, **kwargs)
return wrapper
@verificar_acceso
def cambiar_configuracion(usuario, nueva_configuracion):
print(f"Configuración cambiada a {nueva_configuracion}")
cambiar_configuracion("admin", "Modo oscuro")
cambiar_configuracion("invitado", "Modo claro") Este decorador verificar_acceso comprueba si el usuario tiene permisos para ejecutar la función. Si el usuario no es “admin”, el acceso es denegado y la función no se ejecuta. Este tipo de decorador es fundamental en aplicaciones que requieren control de acceso y permisos.
La salida del código será:
Configuración cambiada a Modo oscuro
Acceso denegado
Indicando que solo la primera llamada se ha ejecutado, mientras que en la segunda se ha denegado el acceso.
Los decoradores también pueden recibir parámetros, lo que permite modificar su comportamiento. Permitiendo modificar el comportamiento del decorador en cada caso. Algo que puede ser interesante, por ejemplo, cuando se desea reintentar la ejecución de una función cuando esta falla debido a una excepción, permitiendo cambiar el número de reintentos. En el siguiente ejemplo, se puede ver la implementación de un decorador parametrizado para controlar el número de reintentos:
def reintentar(veces):
def decorador(func):
def wrapper(*args, **kwargs):
for _ in range(veces):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Error: {e}. Reintentando...")
print(f"Operación fallida después de {veces} intentos")
return wrapper
return decorador
@reintentar(3)
def division(a, b):
return a / b
print(division(10, 2))
print(division(10, 0)) El decorador reintentar toma un argumento veces, que determina cuántas veces debe reintentar la ejecución de la función decorada en caso de fallo. Si la función lanza una excepción, se reintentará el número de veces especificado.
La salida del código será:
5.0
Error: division by zero. Reintentando...
Error: division by zero. Reintentando...
Error: division by zero. Reintentando...
Operación fallida después de 3 intentos
None
Los decoradores en Python son una herramienta extremadamente útil para escribir código limpio, eficiente y mantenible. Al permitir modificar o extender el comportamiento de las funciones de manera elegante, los decoradores facilitan la reutilización del código y la separación de responsabilidades. La capacidad de crear decoradores personalizados permite adaptar las soluciones a las necesidades específicas de cada proyecto, haciendo que el código sea más flexible y robusto.
En la era del dato, las organizaciones se enfrentan al reto de gestionar volúmenes masivos…
En la serie Creación de una API REST con Express y TypeScript construimos una API…
Durante la Segunda Guerra Mundial, la Fuerza Aérea de Estados Unidos quería reforzar sus aviones…
En muchas situaciones —ya sea para grabar un tutorial, tomar capturas de pantalla profesionales, probar…
Imagínate en una sala con un grupo de personas, por ejemplo, en una oficina, un…
En el trabajo diario con ordenadores, es común encontrarse con tareas repetitivas: realizar copias de…
This website uses cookies.