Una guía sobre decoradores de Python para programadores avanzados

Los decoradores de Python son una herramienta potente y flexible para modificar el comportamiento de funciones o métodos. Permiten a los programadores ampliar o alterar la funcionalidad de objetos invocables de una manera clara, legible y fácil de mantener. Este artículo explora conceptos avanzados relacionados con los decoradores de Python, incluidos los decoradores anidados, los argumentos de los decoradores y los decoradores basados ​​en clases.

¿Qué son los decoradores?

Los decoradores son funciones que modifican el comportamiento de otra función. Envuelven otra función para extender su comportamiento sin modificar explícitamente su código. Los decoradores se definen utilizando la sintaxis @decorator_name y se colocan encima de la definición de la función.

Sintaxis básica del decorador

Un decorador simple toma una función como argumento, define una función interna que agrega algún comportamiento y luego devuelve la función interna.

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Funciones decoradoras con argumentos

Los decoradores pueden ser más flexibles si aceptan argumentos. Para crear un decorador de este tipo, es necesario escribir una función que devuelva un decorador. Esto permite agregar un comportamiento más dinámico a los decoradores.

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                func(*args, **kwargs)
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

Decoradores de nidos

Los decoradores se pueden anidar para combinar múltiples comportamientos. Por ejemplo, podemos usar dos o más decoradores en una sola función.

def uppercase_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

def repeat_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result + result
    return wrapper

@repeat_decorator
@uppercase_decorator
def say_word(word):
    return word

print(say_word("hello"))

Decoradores basados ​​en clases

En Python, los decoradores también se pueden implementar como clases mediante el método __call__. Los decoradores basados ​​en clases son útiles cuando se necesita una gestión y un comportamiento de estados más complejos.

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__!r}")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

say_hello()
say_hello()

Uso de functools.wraps para conservar metadatos

Cuando escribes decoradores, la función decorada pierde sus metadatos originales, como su nombre y su cadena de documentación. El decorador functools.wraps se puede utilizar para copiar los metadatos de la función original a la función contenedora.

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Wrapper function executed before", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def display_info(name, age):
    """Displays name and age."""
    print(f"display_info ran with arguments ({name}, {age})")

print(display_info.__name__)  # Output: display_info
print(display_info.__doc__)   # Output: Displays name and age.

Conclusión

Los decoradores de Python son una característica poderosa que permite un diseño flexible de código y la modificación del comportamiento. El uso avanzado, como los decoradores anidados, los decoradores con argumentos y los decoradores basados ​​en clases, puede brindar aún más funcionalidad y legibilidad a los programas de Python. Al comprender y utilizar los decoradores correctamente, los desarrolladores pueden escribir código más conciso, eficiente y legible.