Decorators: Os decoradores do Python

Decorators: Os decoradores do Python

Os decoradores em Python são uma ferramenta poderosa que permite modificar ou aprimorar a funcionalidade de funções, classes e métodos de forma transparente e elegante. Neste artigo, vamos discutir o que são decoradores em Python e como usá-los.

O que são decoradores em Python?

Em Python, um decorador é uma função que recebe outra função como entrada, executa alguma ação com a função de entrada e retorna a função modificada como saída. Na prática, um decorador é usado para envolver uma função em uma camada adicional de funcionalidade.

Por exemplo, suponha que temos uma função simples que calcula a soma de dois números:

def soma(a, b):
    return a + b

Agora, se quisermos adicionar a funcionalidade de imprimir uma mensagem antes de executar a função soma, podemos criar um decorador:

def imprimir_mensagem(func):
    def wrapper(*args, **kwargs):
        print("Executando função...")
        return func(*args, **kwargs)
    return wrapper

Aqui, a função imprimir_mensagem é o decorador que envolve a função soma em uma camada adicional de funcionalidade. A função wrapper é o que chamamos de função interna do decorador, que é responsável por executar a ação adicional antes de chamar a função de entrada func.

Observe que a função wrapper recebe argumentos *args e **kwargs. Isso significa que ele pode receber qualquer número de argumentos posicionais ou nomeados e repassá-los para a função de entrada func.

Para usar o decorador imprimir_mensagem, basta aplicá-lo à função soma:

@imprimir_mensagem
def soma(a, b):
    return a + b

Aqui, usamos a sintaxe @decorator para aplicar o decorador imprimir_mensagem à função soma. Isso é equivalente a chamar a função imprimir_mensagem com a função soma como argumento e substituir a função original pela função modificada que o decorador retorna.

Agora, sempre que chamarmos a função soma, a mensagem "Executando função..." será impressa antes de calcular a soma.

>>> soma(2, 3)
Executando função...
5

Decoradores de classe

Os decoradores em Python também podem ser aplicados a classes. Um decorador de classe é uma função que recebe uma classe como entrada, executa alguma ação com a classe de entrada e retorna a classe modificada como saída.

Por exemplo, podemos ter uma classe simples que define um retângulo:

class Retangulo:
    def __init__(self, largura, altura):
        self.largura = largura
        self.altura = altura

    def area(self):
        return self.largura * self.altura

Agora, se quisermos adicionar a funcionalidade de verificar se a largura e altura são positivas antes de criar um objeto Retangulo, podemos criar um decorador de classe:

def verificar_positividade(cls):
    class RetanguloPositivo:
        def __init__(self, largura, altura):
            if largura <= 0 or altura <= 0:
                raise ValueError("Largura e altura devem ser positivas.")
            self.largura = largura
            self.altura = altura

        def area(self):
            return self.largura * self.altura

    return RetanguloPositivo

Nesse caso, o decorador verificar_positividade recebe a classe Retangulo como entrada, define uma nova classe RetanguloPositivo que estende a classe original e adiciona a funcionalidade de verificar se a largura e a altura são positivas. Por fim, retorna a nova classe RetanguloPositivo.

Para usar o decorador de classe verificar_positividade, basta aplicá-lo à classe Retangulo:

@verificar_positividade
class Retangulo:
    def __init__(self, largura, altura):
        self.largura = largura
        self.altura = altura

    def area(self):
        return self.largura * self.altura

Aqui, usamos a sintaxe @decorator para aplicar o decorador verificar_positividade à classe Retangulo. Isso é equivalente a chamar a função verificar_positividade com a classe Retangulo como argumento e substituir a classe original pela classe modificada que o decorador retorna.

Agora, sempre que criarmos um objeto Retangulo, a verificação de positividade será realizada automaticamente:

>>> r = Retangulo(2, 3)
>>> r.area()
6

>>> r = Retangulo(-2, 3)
ValueError: Largura e altura devem ser positivas.

Decoradores com argumentos

Os decoradores em Python também podem receber argumentos. Isso permite a criação de decoradores mais genéricos e flexíveis que podem ser personalizados de acordo com as necessidades específicas.

Por exemplo, podemos ter um decorador que permite medir o tempo de execução de uma função:

import time

def medir_tempo(func):
    def wrapper(*args, **kwargs):
        inicio = time.time()
        resultado = func(*args, **kwargs)
        fim = time.time()
        print(f"Tempo de execução: {fim - inicio:.2f} segundos")
        return resultado
    return wrapper

Aqui, a função medir_tempo é um decorador que envolve a função de entrada func em uma camada adicional de funcionalidade que mede o tempo de execução. Observe que a função wrapper ainda recebe argumentos *args e **kwargs para que possa lidar com funções com diferentes assinaturas.

No entanto, se quisermos personalizar o texto da mensagem de tempo de execução, podemos criar um decorador com argumentos:

import time

def medir_tempo(texto):
    def decorator(func):
        def wrapper(*args, **kwargs):
            inicio = time.time()
            resultado = func(*args, **kwargs)
            fim = time.time()
            print(f"{texto}: {fim - inicio:.2f} segundos")
            return resultado
        return wrapper
    return decorator

Observe que agora a função medir_tempo é um decorador com um argumento texto, que é usado para personalizar a mensagem de tempo de execução. Em vez de retornar a função wrapper diretamente, a função decorator é retornada em vez disso, permitindo que a função de entrada func seja passada como argumento e retornando a função wrapper que envolve a função de entrada.

Podemos usar esse decorador personalizado da seguinte forma:

@medir_tempo("Tempo de execução:")
def minha_funcao():
    # código da função aqui
    pass

minha_funcao()

Nesse exemplo, a função medir_tempo é chamada com o argumento "Tempo de execução:", retornando o decorador personalizado que pode agora ser usado para decorar a função minha_funcao. Quando a função minha_funcao é executada, o decorador personalizado mede o tempo de execução e imprime a mensagem personalizada:

Tempo de execução: 0.02 segundos

Conclusão

Os decoradores, ou decorators são uma característica poderosa e flexível do Python que permitem a adição de funcionalidades a funções e classes existentes sem modificar seu código. Eles são particularmente úteis para tarefas como a validação de entrada, o cache de resultados, o registro de eventos, a autenticação de usuários e a medição de tempo de execução.

Neste artigo, exploramos os fundamentos dos decoradores em Python, incluindo a sintaxe básica, a aplicação a funções e classes, a criação de decoradores de classe e a personalização de decoradores com argumentos. Com essas ferramentas em mãos, você pode começar a criar seus próprios decoradores personalizados para simplificar e estender seu código Python.