Decore e Simplifique: Dominando os Decorators em Python

Ao centro da imagem temos uma caixa de presente com o nome 'def' representando uma função do python. Essa caixa está sendo envolvida por um laço azul que contém o símbolo '@' representando os decorators no python. Atrás do presente temos o ícone de programação do python.

Decorators são funções que envolvem outras funções, adicionando funcionalidades extras sem modificar o código original. Eles são como “enfeites mágicos” que reutilizamos para economizar tempo e deixar nosso código mais limpo.

Por Que Decorators Importam?

Se você está começando sua jornada com Python, certamente já se deparou com a necessidade de realizar a mesma tarefa repetidamente em diferentes partes do seu código. Talvez seja registrar o tempo de execução de uma função ou verificar se um usuário tem permissão antes de executar uma ação. É aqui que os decorators (decoradores) brilham, sendo uma das ferramentas mais elegantes e poderosas da linguagem.

Pense nos decorators como um serviço de personalização para suas funções. Imagine que você tem um bolo (sua função original). Você pode adicionar uma cobertura de chocolate, um granulado ou uma cereja (as funcionalidades extras) sem precisar refazer o bolo inteiro. Essa capacidade de adicionar comportamento de forma modular, limpa e reutilizável é essencial para escrever código Python profissional.

Como Criar e Usar um Decorator em Python

Para entender como criar e usar um decorator, precisamos saber como o Python lida com as funções. Em Python, as funções são consideradas cidadãos de primeira classe.

O que isso quer dizer? Significa que podemos tratar as funções exatamente como tratamos outros tipos de dados, como números ou textos. Por exemplo, você pode:

  • Atribuí-las a variáveis.
  • Passá-las como argumentos para outras funções.
  • Retorná-las como resultado de outras funções.

📦 A Receita do Decorator: Uma Função Dentro da Outra

Um decorator é, na sua essência, uma função que recebe outra função como argumento, adiciona alguma lógica e, em seguida, retorna uma nova versão dessa função.

Vamos desmembrar a estrutura básica:

  1. Função Externa (O Decorator): Recebe a função original (func) que queremos decorar.
  2. Função Interna (O “Wrapper”): É a função que será realmente executada. Ela contém a lógica extra que você quer adicionar (o “enfeite”). É importante que esta função interna chame a função original (func).
  3. Retorno: A função externa retorna a função interna (wrapper).

🚀 Passo a Passo com um Exemplo Simples

Vamos criar um decorator que simplesmente imprime uma mensagem antes e depois de executar qualquer função, como um registro de atividades.

# 1. Função externa (o decorator)
def meu_decorator(funcao_original):
    # 2. Função interna (o wrapper)
    # Garante que o wrapper possa receber argumentos (*args, **kwargs)
    def wrapper(*args, **kwargs):
        print(f"--- Executando a função: {funcao_original.__name__} ---")
        # Chama a função original e guarda o resultado
        resultado = funcao_original(*args, **kwargs)
        print(f"--- Função {funcao_original.__name__} finalizada ---")
        return resultado # Retorna o resultado da função original
    
    # 3. Retorno
    return wrapper

🏷️ Como Aplicar (O Açúcar Sintático)

A mágica acontece com o símbolo @ (o chamado açúcar sintático). Em vez de fazer uma atribuição manual, usamos o @nome_do_decorator logo acima da definição da função que queremos decorar.

# Aplicando o decorator "meu_decorator" à função "dizer_ola"
@meu_decorator
def dizer_ola(nome):
    """Uma função simples que diz olá."""
    print(f"Olá, {nome}!")
    return f"Saudação para {nome}"

# Usando a função decorada
valor_retornado = dizer_ola("Dev Explica")
print(f"O valor_retornado é: {valor_retornado}")
# Saída:
# --- Executando a função: dizer_ola ---
# Olá, Dev Explica!
# --- Função dizer_ola finalizada ---
#
# O valor_retornado é: Saudação para Dev Explica

O que o @meu_decorator faz é, na verdade, uma simplificação do seguinte:

# dizer_ola = meu_decorator(dizer_ola)

Ou seja, a função dizer_ola original é passada para o meu_decorator, e o resultado (que é a função wrapper) é reatribuído ao nome dizer_ola. Toda vez que chamarmos dizer_ola(), estaremos chamando o wrapper decorado.

Exemplo Prático: Medindo o Tempo ⏱️

Um caso de uso muito comum para um decorator é medir o tempo de execução de uma função, o que é fundamental para otimização de código.

import time

def medir_tempo(func):
    """
    Decorator para medir o tempo de execução de uma função.
    """
    def wrapper(*args, **kwargs):
        inicio = time.time()
        # Chama a função original
        resultado = func(*args, **kwargs) 
        fim = time.time()
        tempo_total = fim - inicio
        print(f"⌛️ A função {func.__name__} levou {tempo_total:.4f} segundos para executar.")
        return resultado
    return wrapper

@medir_tempo
def simular_processamento_pesado(n):
    """Simula um cálculo demorado."""
    soma = 0
    for i in range(n):
        soma += i
    return soma

# Chamada da função. O decorator fará a medição automaticamente.
resultado_processamento = simular_processamento_pesado(1000000) 

# Saída (O tempo de execução será impresso antes do resultado)
# ⌛️ A função simular_processamento_pesado levou 0.0578 segundos para executar.

⚠️ Erros Comuns / Armadilhas

  • Esquecer de Retornar a Função Interna: O decorator deve retornar a função wrapper (a interna), não a execução dela (ou seja, apenas o nome da função sem os parenteses).
  • Não Usar *args e **kwargs: Se a sua função decorada aceitar argumentos, o wrapper sempre deve usar *args e **kwargs para garantir que os argumentos sejam repassados corretamente.
  • Perda de Metadados: Ao decorar uma função, o nome, a documentação (docstring) e os metadados da função original são perdidos, sendo substituídos pelos da função wrapper. Para corrigir isso, use o decorator @functools.wraps do Python no seu wrapper.

✅ Boas Práticas / Dicas Rápidas

  • Use @functools.wraps: Sempre use @functools.wraps(func) no seu wrapper interno para preservar o nome e a docstring da função original. É uma dica de ouro para depuração!
  • Nomeie o Decorator de Forma Clara: O nome deve descrever o que ele adiciona (ex: verificar_permissao, medir_tempo).
  • Mantenha-o Simples: Um decorator deve ter uma única responsabilidade. Se precisar de lógica complexa, crie vários decorators e aplique-os em camadas.

Conclusão: O Poder da Reutilização

Os decorators em Python são a prova de que pequenos ajustes podem gerar grandes impactos na qualidade do seu código. Eles promovem a reutilização de código e seguem o princípio de programação “Não se Repita” (DRY).

Agora que você entendeu o conceito, te desafio a criar o seu próprio decorator, talvez um que verifique se o input de uma função é um número positivo.

Para quem busca ser um especialista Python, o livro Curso Intensivo de Python: uma Introdução Prática e Baseada em Projetos à Programação ensina python com projetos práticos que resolvem problemas reais. Ao adquirir o livro através deste link, você não só investe no seu conhecimento, como também apoia o blog Dev Explica, ajudando a manter a produção de conteúdo didático e de qualidade!

Compre aqui “Curso Intensivo de Python: uma Introdução Prática e Baseada em Projetos à Programação”

Rolar para cima