📋 Table of Contents
O que é um decorador Python?
A Decorador Pythoné um padrão de design que permite adicionar um novo comportamento a uma função existente — sem alterar o código da função. Em Python, funções são objetos de primeira classe: podem ser passadas como argumentos, atribuídas a variáveis e retornadas de outras funções. Os decoradores exploram essa propriedade.
O@sintaxe é apenas açúcar sintático. Quando você escreve:
@my_decorator
def greet(name):
return f"Hello, {name}!"
Python realmente executa:
greet = my_decorator(greet)
O decorador pega a função original, envolve-a em uma nova função com comportamento extra e a retorna. O nomegreetagora aponta para a versão aprimorada.
Seu primeiro decorador: passo a passo
Vamos construir um simplesdecorador de registroque imprime uma mensagem antes e depois de chamar qualquer função.
def log_calls(func):
"""Decorator that logs function entry and exit."""
def wrapper(*args, **kwargs):
print(f"→ Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"← {func.__name__} returned {result!r}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
add(3, 4)
# Output:
# → Calling add with args=(3, 4), kwargs={}
# ← add returned 7
Dividindo isso:
log_callsrecebefunc(a função original)- Ele define um interior
wrapperque aceita*args, **kwargs— então funciona com qualquer assinatura de função - O wrapper chama o original
func, captura o resultado e o retorna log_callsretorna o wrapper – não o resultado de chamá-lo
A correção functools.wraps
Há um bug sutil no decorador acima. Após a decoração,add.__name__retorna'wrapper', não'add'. Isso quebrahelp(), inspecte rastreamentos de pilha. Corrija comfunctools.wraps:
import functools
def log_calls(func):
@functools.wraps(func) # ← copies __name__, __doc__, __module__, etc.
def wrapper(*args, **kwargs):
print(f"→ Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"← {func.__name__} returned {result!r}")
return result
return wrapper
@log_calls
def add(a, b):
"""Add two numbers."""
return a + b
print(add.__name__) # 'add' ✅
print(add.__doc__) # 'Add two numbers.' ✅
@functools.wraps(func)dentro de cada decorador que você escreve. Ignorá-lo causa bugs misteriosos no registro, nas estruturas de teste e nas ferramentas de documentação da API.Decoradores com argumentos
E se você quiser configurar seu decorador? Por exemplo,@retry(times=3). Você precisa de uma camada extra de aninhamento – umfábrica de decoradores:
import functools, time
def retry(times=3, delay=1.0, exceptions=(Exception,)):
"""Retry a function up to `times` times on specified exceptions."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_err = None
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
last_err = e
print(f" Attempt {attempt}/{times} failed: {e}")
if attempt < times:
time.sleep(delay)
raise last_err
return wrapper
return decorator
@retry(times=3, delay=0.5, exceptions=(ConnectionError,))
def fetch_data(url):
# simulate a flaky network call
raise ConnectionError("timeout")
try:
fetch_data("https://api.example.com/data")
except ConnectionError:
print("All retries exhausted.")
A estrutura de três níveis:retry() → decorator() → wrapper(). Chame-o:retry(times=3)retornadecorator, que levafunce retornawrapper.
Empilhando vários decoradores
Você pode aplicar vários decoradores a uma única função. Eles se aplicambaixo para cima(mais interno primeiro):
import functools, time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@timer # applied second (outer)
@log_calls # applied first (inner)
def process(n):
return sum(range(n))
process(1_000_000)
# Output:
# Calling process
# process took 0.0312s
Equivalente a:process = timer(log_calls(process)). A ordem é importante – os decoradores externos envolvem os internos.
Decoradores baseados em classe
Qualquer objeto que pode ser chamado pode ser um decorador. Usar uma classe fornece o estado entre as chamadas:
import functools
class CallCounter:
"""Counts how many times a function is called."""
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} called {self.count} time(s)")
return self.func(*args, **kwargs)
@CallCounter
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice") # called 1 time(s)
say_hello("Bob") # called 2 time(s)
print(say_hello.count) # 2
@staticmethod x @classmethod x @property
Python vem com vários decoradores integrados que você usará constantemente:
class Temperature:
_kelvin_offset = 273.15
def __init__(self, celsius):
self._celsius = celsius
@property
def fahrenheit(self):
"""Computed attribute — no () needed when accessing."""
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
@classmethod
def from_kelvin(cls, kelvin):
"""Factory method — creates instance from Kelvin."""
return cls(kelvin - cls._kelvin_offset)
@staticmethod
def is_valid_celsius(value):
"""Utility — no access to instance or class."""
return value >= -273.15
t = Temperature(100)
print(t.fahrenheit) # 212.0 (property)
t.fahrenheit = 32 # setter
t2 = Temperature.from_kelvin(373.15) # classmethod
print(Temperature.is_valid_celsius(-300)) # False (staticmethod)
| Decorador | Primeiro argumento | Acesso | Caso de uso |
|---|---|---|---|
@property |
self |
Instância + classe | Atributos computados, getters/setters |
@classmethod |
cls |
Somente aula | Métodos de fábrica, construtores alternativos |
@staticmethod |
Nenhum | Nenhum | Funções utilitárias com namespace para classe |
Casos de uso do mundo real
Os decoradores potencializam a maioria das estruturas Python. Aqui estão os padrões de nível de produção:
1. Armazenamento em cache com cache LRU
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # Instant — cached results
print(fibonacci.cache_info()) # CacheInfo(hits=48, misses=51, ...)
2. Protetor de autenticação (estilo Flask/FastAPI)
import functools
def require_auth(func):
"""Decorator for protected routes."""
@functools.wraps(func)
def wrapper(request, *args, **kwargs):
token = request.headers.get("Authorization", "")
if not token.startswith("Bearer "):
return {"error": "Unauthorized"}, 401
return func(request, *args, **kwargs)
return wrapper
@require_auth
def get_user_profile(request):
return {"user": "Alice", "email": "alice@example.com"}
3. Limitador de taxa
import functools, time
from collections import defaultdict, deque
def rate_limit(calls=10, period=60):
"""Allow at most `calls` per `period` seconds per caller."""
history = defaultdict(deque)
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
now = time.time()
key = args[0] if args else "default" # use first arg as caller ID
q = history[key]
while q and now - q[0] > period:
q.popleft()
if len(q) >= calls:
raise RuntimeError(f"Rate limit exceeded: {calls} calls per {period}s")
q.append(now)
return func(*args, **kwargs)
return wrapper
return decorator
@rate_limit(calls=5, period=60)
def send_email(user_id, message):
print(f"Email sent to {user_id}: {message}")
Erros comuns a evitar
- Esquecendo
@functools.wraps– pausashelp(), rastreamentos e ferramentas de teste - Chamando a função no corpo do decorador —
return wrapper()em vez dereturn wrapper - Não usando
*args, **kwargs— quebra qualquer função com parâmetros - Estado padrão mutável compartilhado entre chamadas— use uma variável de fechamento, não um padrão mutável em nível de módulo
- Ausente
return resultem invólucro— engole silenciosamente o valor de retorno da função
# ❌ Wrong — returns None
def broken(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs) # no return!
return wrapper
# ✅ Correct
def fixed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs) # always return
return wrapper
🚀 Aumente o nível de suas habilidades em Python
Agora que você entende os decoradores, explore como eles potencializam estruturas comoestruturando projetos Pythone padrões avançados. Confira nosso guia emExpressões abreviadas do Pythonpara mais truques de código Pythonic.
Perguntas frequentes
O que é um decorador Python?
Um decorador Python é uma função que recebe outra função como entrada e retorna uma versão aprimorada – sem modificar a original. O@sintaxe é uma abreviação defunc = decorator(func).
Quando devo usar decoradores?
Use decoradores para questões transversais: registro, cache, autenticação, limitação de taxa, tempo, validação de entrada — qualquer comportamento que você queira aplicar a múltiplas funções sem repetir código.
O que functools.wraps faz?
Ele copia a função original__name__, __doc__e outros atributos para o wrapper, preservando a identidade para depuração, help() e ferramentas de teste.
Os decoradores podem discutir?
Sim. Adicione uma função de fábrica externa que aceite os argumentos e retorne o decorador real. Padrão:@retry(times=3) → retry()retorna o decorador.
Qual é a diferença entre @staticmethod e @classmethod?
@staticmethodnão recebe nenhum primeiro argumento implícito – é uma função simples em um namespace de classe.@classmethodrecebe a aula (cls) como primeiro argumento, útil para métodos de fábrica.
Como funcionam os decoradores empilhados?
De baixo para cima: o decorador mais próximo dodefé aplicado primeiro.@A @B def f()é igualf = A(B(f)).
🔗 Share this article
✍️ Leave a Comment