Um guia para decoradores Python para programadores avançados

Decoradores Python são uma ferramenta poderosa e flexível para modificar o comportamento de funções ou métodos. Eles permitem que os programadores estendam ou alterem a funcionalidade de objetos chamáveis ​​de uma forma limpa, legível e sustentável. Este artigo explora conceitos avançados relacionados a decoradores Python, incluindo decoradores aninhados, argumentos de decoradores e decoradores baseados em classes.

O que são decoradores?

Decoradores são funções que modificam o comportamento de outra função. Eles encapsulam outra função para estender seu comportamento sem modificar explicitamente seu código. Decoradores são definidos usando a sintaxe @decorator_name e são colocados acima da definição da função.

Sintaxe básica do decorador

Um decorador simples recebe uma função como argumento, define uma função interna que adiciona algum comportamento e, então, retorna a função 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()

Funções Decoradoras com Argumentos

Decoradores podem ser mais flexíveis ao aceitar argumentos. Para criar um decorador assim, você precisa escrever uma função que retorne um decorador. Isso permite adicionar comportamento mais dinâmico aos 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 ninhos

Decoradores podem ser aninhados para combinar múltiplos comportamentos. Por exemplo, podemos usar dois ou mais decoradores em uma única função.

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 baseados em classe

Em Python, decoradores também podem ser implementados como classes usando o método __call__. Decoradores baseados em classe são úteis quando você precisa de gerenciamento de estado e comportamento mais complexos.

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()

Usando functools.wraps para preservar metadados

Quando você escreve decoradores, a função decorada perde seus metadados originais, como seu nome e docstring. O decorador functools.wraps pode ser usado para copiar os metadados da função original para a função wrapper.

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.

Conclusão

Decoradores Python são um recurso poderoso que permite design de código flexível e modificação de comportamento. Uso avançado, como decoradores aninhados, decoradores com argumentos e decoradores baseados em classe, pode fornecer ainda mais funcionalidade e legibilidade para programas Python. Ao entender e utilizar decoradores corretamente, os desenvolvedores podem escrever códigos mais concisos, eficientes e legíveis.