@ antes da definição da função. Primeiramente, decorators envolvem uma função para estendê-la. Por exemplo, @timer pode medir o tempo de execução. Além disso, decorators permitem reutilizar código de forma elegante. Assim, você evita repetir a mesma lógica em várias funções. Consequentemente, o código fica mais seco e manutenível. Quando utilizar decorators? Em logging, cache, autenticação e validação. Também para controle de acesso e medição de performance. Por outro lado, evite decorators para lógica muito específica. Python tem decorators nativos como @staticmethod e @property. Então, vamos explorar como criar e usar decorators personalizados. Três subtítulos guiarão você pelo mundo dos decorators. Portanto, ao final, você adicionará poderosas funcionalidades às suas funções.
Criando decorators básicos
Um decorator é uma função que recebe outra função como argumento. Ele retorna uma nova função que substitui a original. Quando usar decorators básicos? Para adicionar comportamento antes/depois. Por exemplo, logging, timing ou autenticação simples. Além disso, sempre preserve os metadados com@wraps. Exemplo de decorators básicos:
|
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 |
import time from functools import wraps # Decorator para medir tempo de execução def timer(func): """Mede o tempo de execução da função decorada.""" @wraps(func) # Preserva metadados da função original def wrapper(*args, **kwargs): inicio = time.time() resultado = func(*args, **kwargs) fim = time.time() print(f"{func.__name__} levou {fim - inicio:.4f}s") return resultado return wrapper # Decorator para logging def log(func): """Registra chamadas da função.""" @wraps(func) def wrapper(*args, **kwargs): print(f"Chamando {func.__name__} com args={args}, kwargs={kwargs}") resultado = func(*args, **kwargs) print(f"{func.__name__} retornou {resultado}") return resultado return wrapper # Usando os decorators @timer @log def dormir(segundos): time.sleep(segundos) return f"Dormi por {segundos}s" print("=== Decorators Básicos ===\n") resultado = dormir(1) print(f"Resultado: {resultado}") # Decorator para repetir execução def repetir(vezes=3): """Repete a execução da função N vezes.""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): resultados = [] for i in range(vezes): print(f"Execução {i+1}/{vezes}") resultados.append(func(*args, **kwargs)) return resultados return wrapper return decorator @repetir(vezes=2) def saudacao(nome): return f"Olá, {nome}!" print("\n=== Decorator com Parâmetros ===") print(saudacao("Ana")) |
Decorators debug_funcao e debug_oo
|
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
from functools import wraps import traceback import sys from datetime import datetime import inspect def _tratar_excecao(func, assinatura, e): """Tratamento comum de exceções para funções e métodos.""" print(f"ERRO em {func.__name__}: {type(e).__name__}: {str(e)}") if isinstance(e, TypeError): print(f" Dica: Verifique os tipos dos argumentos") print(f" Argumentos recebidos: {assinatura}") print(f" Assinatura esperada: {func.__name__}{func.__signature__ if hasattr(func, '__signature__') else '(...)'}") elif isinstance(e, ValueError): print(f" Dica: Valor inválido fornecido") print(f" Valor problemático: {e.args[0] if e.args else 'N/A'}") elif isinstance(e, ZeroDivisionError): print(f" Dica: Divisão por zero detectada") print(f" Verifique se o divisor não é zero") elif isinstance(e, KeyError): print(f" Dica: Chave não encontrada no dicionário") print(f" Chave procurada: {e.args[0] if e.args else 'N/A'}") elif isinstance(e, IndexError): print(f" Dica: Índice fora dos limites") print(f" Índice problemático: {e.args[0] if e.args else 'N/A'}") elif isinstance(e, AttributeError): print(f" Dica: Objeto não possui o atributo/método solicitado") print(f" Objeto: {repr(e.args[0]) if len(e.args) > 0 else 'N/A'}") print(f" Atributo: {e.args[1] if len(e.args) > 1 else 'N/A'}") elif isinstance(e, FileNotFoundError): print(f" Dica: Arquivo ou diretório não encontrado") print(f" Caminho: {e.filename if hasattr(e, 'filename') else 'N/A'}") elif isinstance(e, PermissionError): print(f" Dica: Permissão negada para acessar o recurso") print(f" Recurso: {e.filename if hasattr(e, 'filename') else 'N/A'}") elif isinstance(e, RecursionError): print(f" Dica: Possível recursão infinita ou muita profundidade") print(f" Limite de recursão atual: {sys.getrecursionlimit()}") else: print(f" Exceção não específica tratada genericamente") print(f"\n Stack trace:") traceback.print_exc(limit=3, file=sys.stdout) print(f"{'='*60}") def debug_funcao(func): """Mostra entrada, saída e tratamento detalhado de exceções da função.""" @wraps(func) def wrapper(*args, **kwargs): # Obtém a assinatura da função sig = inspect.signature(func) # Formata os argumentos recebidos args_repr = [repr(a) for a in args] kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] assinatura_recebida = ", ".join(args_repr + kwargs_repr) # Cabeçalho com nome da função e assinatura esperada print(f"\n{'='*60}") print(f"FUNÇÃO: {func.__name__}") print(f"ASSINATURA ESPERADA: {func.__name__}{sig}") print(f"ASSINATURA RECEBIDA: {func.__name__}({assinatura_recebida})") print(f"DOCSTRING: {func.__doc__.strip() if func.__doc__ else 'Sem documentação'}") print(f"{'-'*60}") try: resultado = func(*args, **kwargs) print(f"RETORNO: {resultado!r}") print(f"STATUS: SUCESSO") print(f"{'='*60}") return resultado except Exception as e: print(f"STATUS: FALHA") _tratar_excecao(func, assinatura_recebida, e) raise return wrapper def debug_oo(metodo): @wraps(metodo) def wrapper(*args, **kwargs): if not args or not hasattr(args[0], '__class__'): raise TypeError( f"@debug_oo só pode ser usado em métodos de instância. " f"'{metodo.__name__}' não é um método de objeto." ) obj = args[0] obj_str = str(obj) if len(obj_str) > 80: obj_str = obj_str[:77] + "..." args_repr = [repr(a) for a in args[1:]] kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] assinatura = ", ".join(args_repr + kwargs_repr) timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3] print(f"\n{'='*70}") print(f" [{timestamp}] OBJETO: {obj.__class__.__name__}") print(f" [{timestamp}] ESTADO: {obj_str}") print(f" [{timestamp}] MÉTODO: {metodo.__name__}({assinatura})") estado_anterior = obj_str try: resultado = metodo(*args, **kwargs) estado_atual = str(obj) if len(estado_atual) > 80: estado_atual = estado_atual[:77] + "..." if estado_anterior != estado_atual: print(f" [{timestamp}] METODO EXECUTADO COM SUCESSO") print(f" [{timestamp}] NOVO ESTADO: {estado_atual}") if resultado is not None: print(f" [{timestamp}] RETORNO: {resultado!r}") else: print(f" [{timestamp}] EXECUTADO -> retorno: {resultado!r}") print(f"{'='*70}") return resultado except Exception as e: _tratar_excecao(metodo, assinatura, e) raise return wrapper # ==================== TESTES ==================== print("=" * 80) print("TESTES DO DECORATOR debug_funcao") print("=" * 80) print("\n[Teste 1] Funcao normal com assinatura clara") @debug_funcao def soma(a, b, multiplicador=1): """Soma dois números e multiplica o resultado.""" return (a + b) * multiplicador try: resultado = soma(3, 5, multiplicador=2) print(f"Resultado final: {resultado}") except: pass print("\n[Teste 2] Funcao com multiplos parametros") @debug_funcao def criar_usuario(nome, idade, email=None, ativo=True): """Cria um novo usuário no sistema.""" return { 'nome': nome, 'idade': idade, 'email': email, 'ativo': ativo } try: usuario = criar_usuario("João", 30, email="joao@email.com", ativo=True) print(f"Usuario criado: {usuario['nome']}") except: pass print("\n[Teste 3] TypeError (argumento invalido)") @debug_funcao def dividir(a, b): """Divide a por b.""" return a / b try: dividir(10, "2") except Exception as e: print(f"Excecao capturada: {type(e).__name__}") print("\n[Teste 4] ZeroDivisionError") try: dividir(10, 0) except Exception as e: print(f"Excecao capturada: {type(e).__name__}") print("\n[Teste 5] Funcao sem documentacao") @debug_funcao def sem_doc(x, y): return x * y try: sem_doc(4, 5) except: pass print("\n[Teste 6] ValueError") @debug_funcao def raiz_quadrada(numero): """Calcula a raiz quadrada de um número.""" if numero < 0: raise ValueError(f"Não é possível calcular raiz de {numero}") return numero ** 0.5 try: raiz_quadrada(-9) except Exception as e: print(f"Excecao capturada: {type(e).__name__}") # ==================== TESTES DO debug_oo ==================== print("\n" + "=" * 80) print("TESTES DO DECORATOR debug_oo") print("=" * 80) print("\n[Teste 7] Classe com metodos") class Banco: def __init__(self, nome): self.nome = nome self.saldo = 0 def __str__(self): return f"Banco {self.nome}: R${self.saldo:.2f}" @debug_oo def depositar(self, valor): """Deposita valor na conta.""" if valor <= 0: raise ValueError("Valor deve ser positivo") self.saldo += valor return self.saldo @debug_oo def sacar(self, valor): """Saca valor da conta.""" if valor <= 0: raise ValueError("Valor deve ser positivo") if valor > self.saldo: raise ValueError("Saldo insuficiente") self.saldo -= valor return self.saldo banco = Banco("Meu Banco") print("\n--- Operacoes com sucesso ---") banco.depositar(1000) banco.sacar(300) print("\n--- Operacoes com erro ---") try: banco.depositar(-50) except Exception as e: print(f"Erro esperado: {type(e).__name__}") try: banco.sacar(10000) except Exception as e: print(f"Erro esperado: {type(e).__name__}") print("\n[Teste 8] Metodo com retorno complexo") class Processador: def __init__(self): self.dados = [] def __str__(self): return f"Processador ({len(self.dados)} itens)" @debug_oo def processar_lista(self, lista, multiplicador=2): """Processa uma lista multiplicando cada elemento.""" self.dados = [x * multiplicador for x in lista] return self.dados proc = Processador() resultado = proc.processar_lista([1, 2, 3, 4], multiplicador=3) print(f"Resultado processado: {resultado}") print("\n" + "=" * 80) print("TODOS OS TESTES CONCLUIDOS") print("=" * 80) |
Decorators com argumentos e parâmetros
Decorators podem receber argumentos para serem configuráveis. A estrutura tem três níveis: decorator, wrapper interna e função. Quando usar decorators com argumentos? Em comportamentos parametrizáveis. Por exemplo, limites de tempo, contagem de tentativas ou níveis de log. Assim, você cria decorators reutilizáveis e flexíveis. Exemplo de decorators com argumentos:|
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
from functools import wraps import time # Decorator com argumento (máximo de tentativas) def retry(max_tentativas=3, delay=1): """Tenta executar a função novamente em caso de erro.""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for tentativa in range(max_tentativas): try: return func(*args, **kwargs) except Exception as e: if tentativa == max_tentativas - 1: raise print(f"Tentativa {tentativa+1} falhou: {e}") time.sleep(delay) return None return wrapper return decorator @retry(max_tentativas=3, delay=0.5) def funcao_instavel(): import random if random.random() < 0.7: raise ValueError("Erro aleatório!") return "Sucesso!" print("=== Decorator com Argumentos (retry) ===") try: print(funcao_instavel()) except ValueError: print("Falha após todas as tentativas") # Decorator para timeout def timeout(segundos): """Cancela a função se exceder o tempo limite.""" import signal def decorator(func): @wraps(func) def wrapper(*args, **kwargs): def handler(signum, frame): raise TimeoutError(f"Função excedeu {segundos}s") signal.signal(signal.SIGALRM, handler) signal.alarm(segundos) try: resultado = func(*args, **kwargs) finally: signal.alarm(0) return resultado return wrapper return decorator @timeout(2) def operacao_lenta(): time.sleep(3) return "Nunca chegará" print("\n=== Decorator com Timeout ===") try: print(operacao_lenta()) except TimeoutError as e: print(f"Timeout: {e}") # Decorator para cache (memorização) def cache(maxsize=128): """Cache simples para resultados de funções.""" def decorator(func): cache_dict = {} @wraps(func) def wrapper(*args, **kwargs): # Cria chave a partir dos argumentos chave = (args, tuple(sorted(kwargs.items()))) if chave in cache_dict: print(f"Cache hit para {func.__name__}{args}") return cache_dict[chave] print(f"Cache miss para {func.__name__}{args}") resultado = func(*args, **kwargs) # Limita tamanho do cache if len(cache_dict) >= maxsize: cache_dict.pop(next(iter(cache_dict))) cache_dict[chave] = resultado return resultado return wrapper return decorator @cache(maxsize=2) def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) print("\n=== Decorator com Cache ===") print(f"fibonacci(10): {fibonacci(10)}") print(f"fibonacci(10) (segunda vez): {fibonacci(10)}") |
Decorators práticos: autenticação e validação
Decorators são excelentes para validação e controle de acesso. Eles verificam pré-condições antes de executar a função. Quando usar esses decorators? Em APIs, rotas web e sistemas de permissão. Por exemplo, verificar se usuário está logado ou dados são válidos. Além disso, eles centralizam a lógica de segurança. Exemplo de decorators práticos:|
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
from functools import wraps # Decorator para validação de tipos def type_check(**tipos): """Valida os tipos dos argumentos.""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # Combina args e kwargs nomes_args = func.__code__.co_varnames[:len(args)] valores = dict(zip(nomes_args, args)) valores.update(kwargs) for nome, tipo_esperado in tipos.items(): if nome in valores: valor = valores[nome] if not isinstance(valor, tipo_esperado): raise TypeError( f"Argumento '{nome}' deve ser {tipo_esperado.__name__}, " f"recebeu {type(valor).__name__}" ) return func(*args, **kwargs) return wrapper return decorator @type_check(a=int, b=int) def somar_numeros(a, b): return a + b print("=== Decorator de Validação de Tipos ===") print(f"somar_numeros(5, 3): {somar_numeros(5, 3)}") try: print(somar_numeros(5, "3")) except TypeError as e: print(f"Erro: {e}") # Decorator para autenticação (simulada) def requer_login(func): """Verifica se o usuário está autenticado.""" @wraps(func) def wrapper(usuario, *args, **kwargs): if not usuario.get("autenticado", False): raise PermissionError("Usuário não autenticado") return func(usuario, *args, **kwargs) return wrapper def requer_permissao(permissao_necessaria): """Verifica se o usuário tem uma permissão específica.""" def decorator(func): @wraps(func) def wrapper(usuario, *args, **kwargs): if not usuario.get("autenticado", False): raise PermissionError("Usuário não autenticado") if permissao_necessaria not in usuario.get("permissoes", []): raise PermissionError( f"Usuário não tem permissão '{permissao_necessaria}'" ) return func(usuario, *args, **kwargs) return wrapper return decorator # Usuários de exemplo usuario_comum = {"nome": "Ana", "autenticado": True, "permissoes": ["ler"]} usuario_admin = {"nome": "Carlos", "autenticado": True, "permissoes": ["ler", "escrever", "deletar"]} usuario_nao_auth = {"nome": "João", "autenticado": False} @requer_login def perfil(usuario): return f"Perfil de {usuario['nome']}" @requer_permissao("escrever") def criar_post(usuario, titulo): return f"Post '{titulo}' criado por {usuario['nome']}" print("\n=== Decorator de Autenticação ===") print(perfil(usuario_comum)) print(criar_post(usuario_admin, "Meu post")) try: print(criar_post(usuario_comum, "Tentativa")) except PermissionError as e: print(f"Erro: {e}") # Decorator para rate limiting (limite de chamadas) def rate_limit(max_chamadas, janela_segundos): """Limita o número de chamadas por janela de tempo.""" def decorator(func): chamadas = [] @wraps(func) def wrapper(*args, **kwargs): agora = time.time() # Remove chamadas antigas while chamadas and chamadas[0] < agora - janela_segundos: chamadas.pop(0) if len(chamadas) >= max_chamadas: raise RuntimeError( f"Limite de {max_chamadas} chamadas em {janela_segundos}s excedido" ) chamadas.append(agora) return func(*args, **kwargs) return wrapper return decorator @rate_limit(max_chamadas=3, janela_segundos=5) def api_request(url): return f"Requisição para {url}" print("\n=== Decorator de Rate Limit ===") for i in range(5): try: print(api_request(f"url_{i}")) except RuntimeError as e: print(f"Erro: {e}") time.sleep(1) # Empilhando múltiplos decorators @timer @log @retry(max_tentativas=2, delay=0.1) def operacao_critica(): import random if random.random() < 0.5: raise ValueError("Falha aleatória") return "Operação bem-sucedida!" print("\n=== Múltiplos Decorators ===") try: resultado = operacao_critica() print(f"Resultado: {resultado}") except ValueError: print("Operação falhou após tentativas") |