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.

Fuente

Plataforma de cursos gratis sobre programación