Decorators são funções que modificam o comportamento de outras funções. Eles representam um recurso elegante da linguagem Python. Com um decorator, você adiciona funcionalidades sem alterar o código original. Isso promove o princípio DRY (Don’t Repeat Yourself). Além disso, eles resolvem problemas comuns de logging, cache e autenticação. Cada decorator atende uma necessidade específica de forma limpa. Por essa razão, eles se tornaram ferramentas indispensáveis. Assim, qualquer desenvolvedor Python deveria dominá-los.
Decorator para logging: rastreando execuções
O logging registra quando uma função é chamada e seus argumentos. Ele também captura o tempo de execução ou possíveis erros. Portanto, você obtém uma trilha útil para depuração em produção. Geralmente, aplicamos logging em funções críticas ou em APIs. Por exemplo, você pode logar todas as requisições a um banco. O decorator elimina a repetição manual de prints. Consequentemente, a manutenção do código fica muito mais simples. Além disso, você pode ativar ou desativar logs globalmente.
Uma fórmula mental para logging é:
Decorator para cache: acelerando repetições
O cache armazena resultados de funções com os mesmos argumentos. Quando a mesma chamada ocorre novamente, retornamos o valor imediato. Isso poupa tempo computacional em cálculos pesados. Operações de rede ou consultas lentas se beneficiam enormemente. Por outro lado, evite cache em funções que alteram estado externo. Um exemplo clássico é o cálculo recursivo de Fibonacci. Sem cache, sua complexidade se torna exponencial. Com cache, a complexidade cai para linear. Portanto, o cache é uma otimização poderosa.
A lógica do cache pode ser expressa assim:
functools.lru_cache para essa tarefa.
Use cache quando sua função for pura e repetitiva.
Primeiro, identifique gargalos de desempenho no seu sistema.
Depois, adicione o decorator de cache nessas funções.
Assim, você ganha velocidade sem alterar a lógica original.
Decorator para autenticação: protegendo acesso
A autenticação verifica se o usuário possui permissão antes da execução. Esse decorator aparece frequentemente em frameworks web como Flask. Ele pode checar um token, uma sessão ativa ou um papel. Se a verificação falhar, o decorator lança uma exceção. Dessa forma, o sistema impede ações não autorizadas. A lógica de segurança fica centralizada em um único local. Além disso, você nunca deve confiar apenas no front-end. A validação no back-end continua sendo obrigatória. Portanto, decorators de autenticação são indispensáveis.
Por exemplo, rotas administrativas ou exclusão de dados exigem papel admin. A função original roda exclusivamente para usuários autorizados. Caso contrário, o decorator retorna uma mensagem de erro. Esse padrão é amplamente adotado em sistemas reais. Sua clareza facilita auditorias de segurança. Ademais, você pode compor múltiplos decorators de autenticação. Isso permite criar hierarquias de permissões sofisticadas. Primeiro, defina os papéis disponíveis no sistema.
Código completo dos três decorators em python
Abaixo, apresentamos um exemplo funcional com logging, cache e autenticação.
Cada decorator aparece separadamente para facilitar o entendimento.
Observe como aplicamos eles com o símbolo @.
A ordem dos decorators influencia o comportamento final.
Primeiro, avalie qual camada deve executar primeiro.
Geralmente, autenticação vem antes de logging.
Cache geralmente fica na camada mais interna.
Teste cada combinação para evitar surpresas indesejadas.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
import time import functools from datetime import datetime # 1. Decorator de logging def log_execution(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f"[{datetime.now()}] Chamando {func.__name__} com {args}") inicio = time.time() resultado = func(*args, **kwargs) duracao = time.time() - inicio print(f"[{datetime.now()}] {func.__name__} retornou {resultado} em {duracao:.4f}s") return resultado return wrapper # 2. Decorator de cache simples (dicionário) def simple_cache(func): cache = {} @functools.wraps(func) def wrapper(*args): if args in cache: print(f"Cache hit para {func.__name__}{args}") return cache[args] print(f"Cache miss para {func.__name__}{args} - calculando...") resultado = func(*args) cache[args] = resultado return resultado return wrapper # 3. Decorator de autenticação (simulado) def require_auth(role="user"): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # Simula um usuário logado (em sistemas reais, vem da sessão) current_user = getattr(wrapper, "user", {"role": "admin"}) if current_user.get("role") != role: raise PermissionError(f"Acesso negado. Requer papel: {role}") return func(*args, **kwargs) return wrapper return decorator # Aplicando os decorators @log_execution @simple_cache def fib(n): """Cálculo de Fibonacci (recursivo) com cache""" if n < 2: return n return fib(n-1) + fib(n-2) @require_auth(role="admin") @log_execution def delete_user(user_id): print(f"Usuário {user_id} foi deletado.") return True # Testes print("=== Teste Fibonacci ===") print(fib(10)) # primeira vez: cache miss, calcula print(fib(10)) # cache hit: retorna instantâneo print("\n=== Teste Autenticação ===") # Simula um usuário admin no decorator (apenas demonstração) delete_user.user = {"role": "admin"} delete_user(42) # deve passar try: delete_user.user = {"role": "guest"} delete_user(99) # levantará PermissionError except PermissionError as e: print(f"Erro capturado: {e}") |
Neste código, o logging mostra cada chamada e seu respectivo tempo. O cache evita recálculos desnecessários da sequência de Fibonacci. A autenticação impede ações de usuários não autorizados. Primeiro, observe que as funções originais permanecem simples. Elas focam exclusivamente em sua tarefa principal. As preocupações transversais ficaram isoladas nos decorators. Essa abordagem segue as boas práticas da comunidade Python. Além disso, decorators empilhados executam de baixo para cima. Portanto, preste atenção na ordem ao combiná-los. Finalmente, teste sempre cada decorator individualmente. Isso garante que eles funcionem como você espera. Assim, você constrói sistemas mais robustos e organizados.