Los decoradores se implementan utilizando funciones o clases y se utilizan para envolver o decorar el objeto de destino con funcionalidad adicional.
¿Por qué los decoradores son importantes en Python?
Los decoradores juegan un papel crucial en la programación en Python por varias razones:
a) Reutilización del código:
Los decoradores permiten separar las preocupaciones transversales, como el registro, la validación, la autenticación o el almacenamiento en caché, de la lógica central de las funciones o clases. Esto favorece la reutilización del código y la modularidad.
b) Facilidad de lectura y mantenimiento:
Los decoradores proporcionan una forma limpia y concisa de añadir funcionalidad al código existente sin saturar la implementación original. Mejoran la legibilidad del código aislando comportamientos específicos y facilitando su comprensión y mantenimiento.
c) Metaprogramación y extensibilidad:
Los decoradores permiten la modificación dinámica del código en tiempo de ejecución, lo que posibilita técnicas avanzadas de metaprogramación. Proporcionan un mecanismo flexible para ampliar y personalizar el comportamiento de funciones o clases sin modificar su definición original.
Entendiendo los Decoradores de Funciones en phyton
Los decoradores de funciones en Python permiten modificar el comportamiento de una función sin modificar directamente su código fuente. Se implementan utilizando funciones o clases de orden superior. Cuando se decora una función, se pasa como argumento al decorador y éste devuelve una versión modificada de la función.
Sintaxis y uso de los decoradores:
Los decoradores se implementan utilizando el símbolo "@"
seguido del nombre de la función o clase decoradora. Se colocan antes de la definición de la función que se va a decorar. La función/clase decoradora se encarga de modificar o mejorar el comportamiento de la función de destino. He aquí un ejemplo:
@decorator
def my_function():
# Function code
Decoración de funciones con argumentos
Los decoradores pueden aceptar argumentos, lo que permite personalizar el comportamiento de la función decorada. Para conseguirlo, puede crear una función decoradora de fábrica que acepte argumentos y devuelva la función decoradora. La función decoradora puede entonces envolver la función original y aplicar el comportamiento deseado basándose en los argumentos.
Ejemplo:
def decorator_factory(argument):
def decorator(func):
def wrapper(*args, **kwargs):
# Pre-decoration logic
print("Decorator argument:", argument)
result = func(*args, **kwargs)
# Post-decoration logic
return result
return wrapper
return decorator
@decorator_factory("example_argument")
def my_function():
# Function code
print("Inside my_function")
# Test the decorated function
my_function()
Explicación:
- La
decorator_factory
es una función que toma un argumento y devuelve una función decoradora. - La función decoradora toma una función de destino como argumento y devuelve una función envoltorio.
- La función envolvente realiza la lógica previa a la decoración, como imprimir el argumento del decorador.
A continuación, la envoltura llama a la función original (func)
con los argumentos proporcionados y almacena el resultado.
Por último, la envoltura realiza la lógica posterior a la decoración y devuelve el resultado.
mi_función está decorada con el decorador
decorator_factory
("ejemplo_argumento"). Esto significa que se llama a la función decorator_factory
con el argumento "ejemplo_argumento", y devuelve la función decoradora real.
La función decorador envuelve la función mi_función
añadiendo la lógica de pre-decoración para imprimir el argumento del decorador.
Cuando ejecutas mi_función()
, ejecuta la función envolvente, que imprime el argumento del decorador y luego ejecuta el código original dentro de mi_función.
Ejemplo :
Ejemplo de decoración de funciones con argumentos es la validación de entrada en una aplicación web. Consideremos un escenario en el que usted tiene un punto final de API web que recibe la entrada del usuario y necesita validar la entrada antes de procesarla posteriormente.
He aquí un ejemplo de implementación:
def validate_input(*expected_args):
def decorator(func):
def wrapper(*args, **kwargs):
# Perform input validation
for arg, expected_type in zip(args, expected_args):
if not isinstance(arg, expected_type):
raise ValueError(f"Invalid input type. Expected {expected_type.__name__}, but got {type(arg).__name__}.")
return func(*args, **kwargs)
return wrapper
return decorator
@validate_input(str, int)
def process_user_input(name, age):
# Process the validated input
print(f"User: {name}, Age: {age}")
# Test the decorated function
process_user_input("John Doe", 25)
En este ejemplo, la fábrica del decorador validate_input
toma como argumentos los tipos de argumentos esperados.
La función decoradora, decorator, realiza la validación de la entrada comprobando los tipos de los argumentos pasados a la función decorada, wrapper
. Si alguno de los argumentos no coincide con los tipos esperados, se genera un ValueError
.
La función decorada, process_user_input
, está decorada con @validate_input(str, int)
, indicando que los tipos de argumentos esperados son str (para el nombre) e int (para la edad).
Cuando ejecutes este código y llames a process_user_input
("John Doe", 25), se ejecutará la función wrapper.
Los valores de entrada serán validados contra los tipos de argumento esperados. Si la entrada es válida, se ejecutará el código de la función original dentro de process_user_input
, imprimiendo el nombre y la edad del usuario. Si la entrada no coincide con los tipos esperados, se producirá un error ValueError.
Este ejemplo demuestra cómo los decoradores con argumentos pueden ser utilizados para validar la entrada del usuario en una aplicación web, asegurando que la entrada se adhiere a los tipos o formatos esperados antes de su posterior procesamiento.
Encadenamiento de varios decoradores
Puede aplicar varios decoradores a una función apilándolos mediante la sintaxis de decoradores. Los decoradores se aplican de abajo a arriba, es decir, el decorador más cercano a la función se aplica primero, seguido del siguiente decorador, y así sucesivamente. Esto le permite aplicar múltiples capas de comportamiento a la función.
Ejemplo:
@decorator1
@decorator2
@decorator3
def my_function():
# Function code
import time
# Mock function for authentication check
def is_authenticated():
return True
class UnauthorizedException(Exception):
pass
class RateLimitExceededException(Exception):
pass
def authenticate(func):
def wrapper(*args, **kwargs):
# Perform authentication logic
if not is_authenticated():
raise UnauthorizedException("Authentication failed")
return func(*args, **kwargs)
return wrapper
def rate_limit(max_requests, interval):
def decorator(func):
request_count = 0
last_request_time = None
def wrapper(*args, **kwargs):
nonlocal request_count, last_request_time
# Check rate limit
if last_request_time and time.time() - last_request_time < interval:
if request_count >= max_requests:
raise RateLimitExceededException("Rate limit exceeded")
else:
request_count = 0
# Update request count and time
request_count += 1
last_request_time = time.time()
return func(*args, **kwargs)
return wrapper
return decorator
@authenticate
@rate_limit(max_requests=100, interval=60)
def protected_api_endpoint():
# API endpoint implementation
return "Success"
# Test the chained decorators
response = protected_api_endpoint()
print(response)
En este ejemplo, se define primero el decorador authenticate, que realiza la lógica de autenticación. Comprueba si el usuario está autenticado antes de permitir el acceso a la función decorada. Si la autenticación falla, se lanza una excepción.
A continuación se define el decorador rate_limit
, que impone un límite de velocidad a la función decorada.
Limita el número de peticiones que se pueden realizar en un intervalo determinado. Si se supera el límite de velocidad, se produce una excepción.
La función protected_api_endpoint
está decorada con @authenticate
y @rate_limit(max_requests=100, interval=60)
, lo que significa que ambos decoradores se aplican a la función.
Cuando se llama a la función, primero se comprueba la autenticación y, si se realiza correctamente, se aplica el límite de velocidad. Si ambas comprobaciones pasan, se ejecuta la implementación de la función y se devuelve el resultado.
Cuando ejecutes este código y llames a protected_api_endpoint()
, se ejecutarán los decoradores encadenados. Si el usuario está autenticado y no se supera el límite de velocidad, se ejecutará la implementación del punto final de la API y se imprimirá el resultado.
Este ejemplo demuestra cómo encadenar varios decoradores puede utilizarse en un escenario real para aplicar tanto la autenticación como la limitación de velocidad a un punto final de API web.
Aplicando múltiples capas de decoradores, puedes añadir diferentes niveles de funcionalidad o comportamiento a tus funciones, garantizando seguridad, fiabilidad y rendimiento en tu aplicación.