Decoradores em Python: o que são, como funcionam e como criar o seu
Decorador em Python é uma função que recebe outra função e devolve uma nova, geralmente um invólucro que acrescenta comportamento antes ou depois da chamada: registro de logs, medição de tempo, cache, verificação de permissões. A notação @decorator acima de def é apenas um atalho para func = decorator(func); para que o invólucro mantenha o nome e a docstring da função original, usa-se functools.wraps.
Um decorador em Python é uma função que recebe uma função e devolve outra — em geral um invólucro em torno da original. Assim se acrescenta comportamento, como registro de logs, medição de tempo, cache ou novas tentativas, sem alterar o código da função. Os decoradores são escritos com a notação @nome logo acima da declaração da função e aparecem em todo lugar: no Flask e no FastAPI, no pytest, na biblioteca padrão.
O que é um decorador em linguagem simples
Imagine um presente e o papel de embrulho. O presente (a função) continua o mesmo, mas por fora aparece uma camada que age primeiro. O decorador é exatamente esse embrulho: na chamada, primeiro executa o código do invólucro, que chama a função original e pode fazer algo com o resultado.
Isso funciona porque, em Python, funções são objetos comuns. Elas podem ser passadas a outras funções, devolvidas por funções e declaradas dentro de funções. Os decoradores são construídos sobre essas três propriedades.
Como funciona um decorador: o @ é açúcar sintático
Veja um decorador simples, que imprime uma mensagem antes e depois da chamada:
def log_calls(func):
def wrapper():
print(f"Chamando {func.__name__}")
result = func()
print(f"{func.__name__} concluída")
return result
return wrapper
@log_calls
def hello():
print("Olá!")
hello()
Chamando hello
Olá!
hello concluída
A notação @log_calls acima do def é totalmente equivalente à linha que vem depois da declaração:
def hello():
print("Olá!")
hello = log_calls(hello)
Agora o nome hello aponta para o wrapper, e a função original vive dentro do closure — o wrapper guarda a variável func da função externa. Um detalhe importante: o decorador é aplicado uma única vez, no momento em que o def é executado, ou seja, na importação do módulo; já o invólucro é executado a cada chamada.
Closures e decoradores
Closure é uma função interna que guarda as variáveis da função externa mesmo depois de ela ter terminado. Sem closures não existiriam decoradores: é justamente assim que o wrapper tem acesso a func. No closure também é possível guardar estado, por exemplo um contador de chamadas:
def count_calls(func):
calls = 0
def wrapper(*args, **kwargs):
nonlocal calls
calls += 1
print(f"{func.__name__}: chamada nº {calls}")
return func(*args, **kwargs)
return wrapper
Como escrever um decorador em Python: modelo universal
O primeiro exemplo tem duas falhas: o invólucro não aceita argumentos e «engole» o nome da função original. Um modelo funcional, com o qual vale começar qualquer decorador, fica assim:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# código antes da chamada
result = func(*args, **kwargs)
# código depois da chamada
return result
return wrapper
Três regras do modelo:
*args, **kwargs— o invólucro aceita quaisquer argumentos e os repassa sem alterações.return result— não esquecer de devolver o resultado, senão a função decorada sempre devolveráNone.@functools.wraps(func)— transferir os metadados da função original para o invólucro.
Exemplo de decorador: medição do tempo de execução.
import functools
import 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__} levou {elapsed:.3f} s")
return result
return wrapper
@timer
def slow_sum(n):
return sum(range(n))
slow_sum(10_000_000) # slow_sum levou 0.142 s
Para que serve functools.wraps
Sem wraps, a função decorada perde o nome, a docstring e outros atributos — de fora se vê o wrapper:
def plain(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@plain
def add(a, b):
"""Soma dois números."""
return a + b
print(add.__name__, add.__doc__) # wrapper None
Com @functools.wraps(func) o invólucro recebe __name__, __doc__, __module__, __qualname__ e __annotations__ da função original, e no atributo __wrapped__ fica a referência ao original. Isso é importante para depuração e para os tracebacks, para help(), para frameworks que registram funções pelo nome e para inspect.signature, que mostra a assinatura real.
Decorador com parâmetros (com argumentos)
Às vezes o decorador precisa de configurações: @retry(times=3), @app.route("/"). Para isso acrescenta-se mais um nível de aninhamento. A função externa recebe os parâmetros e devolve o decorador de verdade — esse tipo de construção é chamado de fábrica de decoradores.
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def ping():
print("ping")
ping() # ping, ping, ping — três linhas
Ordem das chamadas: repeat(times=3) devolve decorator, que é aplicado a ping e devolve wrapper. Em outras palavras, @repeat(times=3) equivale a ping = repeat(times=3)(ping).
Um exemplo prático — repetir em caso de erro:
import functools
import time
def retry(times=3, delay=1.0, exceptions=(Exception,)):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except exceptions:
if attempt == times:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=5, delay=0.5, exceptions=(ConnectionError,))
def fetch_rate():
...
Vários decoradores na mesma função
Decoradores podem ser empilhados. Eles são aplicados de baixo para cima: o mais próximo do def é o primeiro.
@timer
@log_calls
def report():
...
# o mesmo que:
report = timer(log_calls(report))
Na chamada, a camada externa (timer) é executada primeiro, por isso ela também medirá o tempo de trabalho de log_calls. A ordem importa, por exemplo, quando um decorador verifica permissões de acesso e outro faz cache do resultado.
Decoradores padrão e embutidos do Python
Muitos decoradores úteis já estão na linguagem e na biblioteca padrão:
| Decorador | O que faz |
|---|---|
@property | transforma um método em atributo somente de leitura; com @x.setter — em atributo controlado |
@staticmethod | método de classe sem self, função comum dentro do namespace da classe |
@classmethod | o método recebe a classe cls em vez da instância — assim se escrevem construtores alternativos |
@functools.cache, @functools.lru_cache | guardam em cache os resultados por argumentos |
@functools.wraps | copia os metadados da função para o invólucro |
@dataclasses.dataclass | gera __init__, __repr__ e __eq__ para a classe |
@contextlib.contextmanager | transforma um gerador em gerenciador de contexto para o with |
Cache: functools.cache e lru_cache
O decorador de cache memoriza o resultado para cada conjunto de argumentos e, numa nova chamada, devolve-o sem recalcular. O exemplo clássico são os números de Fibonacci recursivos, que sem cache demoram um tempo exponencial:
import functools
@functools.cache
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(100)) # 354224848179261915075
functools.cache (surgiu no Python 3.9) guarda todos os resultados sem limite. Já @functools.lru_cache(maxsize=128) mantém apenas os registros mais recentes e descarta os antigos — é mais seguro para serviços que funcionam por muito tempo. As estatísticas são vistas com fib.cache_info(), e o cache é limpo com fib.cache_clear(). Os argumentos dessas funções precisam ser hasheáveis: uma lista como argumento gera TypeError: unhashable type: 'list'. E cachear vale apenas para funções puras — aquelas que, com os mesmos argumentos, sempre devolvem o mesmo resultado e não têm efeitos colaterais.
O que faz o decorador property
@property permite acessar um método como se fosse atributo, sem parênteses, e, se necessário, controlar a atribuição de valores:
class Account:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("O saldo não pode ser negativo")
self._balance = value
acc = Account(100)
print(acc.balance) # 100
acc.balance = -5 # ValueError
staticmethod e classmethod
class Money:
def __init__(self, cents):
self.cents = cents
@classmethod
def from_reais(cls, reais):
return cls(round(reais * 100))
@staticmethod
def is_valid(value):
return value >= 0
m = Money.from_reais(12.5)
print(m.cents, Money.is_valid(-1)) # 1250 False
É o @classmethod que permite ao método trabalhar com a própria classe, e não com as instâncias: from_reais cria corretamente também um objeto de uma subclasse.
Decoradores de classes e classes como decoradores
Um decorador pode ser aplicado a uma classe: ele recebe a classe e devolve a classe (a mesma ou uma alterada). É assim que funciona o @dataclass. Um exemplo simples — um registro de classes:
registry = {}
def register(cls):
registry[cls.__name__] = cls
return cls
@register
class CsvExporter:
pass
print(registry) # {'CsvExporter': <class '__main__.CsvExporter'>}
O caso inverso é uma classe usada como decorador. Serve qualquer objeto com o método __call__; o estado é convenientemente guardado nos atributos da instância:
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs):
self.calls += 1
return self.func(*args, **kwargs)
@CountCalls
def tick():
pass
tick(); tick()
print(tick.calls) # 2
Decorador assíncrono
Para funções async def o invólucro também deve ser uma corrotina e aguardar o resultado com await; caso contrário, será devolvido um objeto de corrotina ainda não executado:
import functools
def log_async(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
print(f"Início de {func.__name__}")
result = await func(*args, **kwargs)
print(f"Concluído {func.__name__}")
return result
return wrapper
Para que servem os decoradores em Python
- Lógica transversal — logs, métricas e medição de tempo sem copiar código para cada função.
- Cache —
@functools.cachepara cálculos caros e puros. - Verificações — permissões, validação de argumentos, autorização em handlers web.
- Registro — rotas no Flask e no FastAPI (
@app.get("/")), fixtures do pytest. - Confiabilidade — novas tentativas, timeouts, tratamento de erros.
A principal vantagem é a separação de responsabilidades: a função cuida da sua tarefa, e o comportamento auxiliar é acoplado com uma linha e removido com a mesma facilidade.
Erros típicos
- O invólucro não devolve o resultado — a função depois de decorada retorna
None. - Sem
*args, **kwargs—TypeErrorao chamar com argumentos. - Esqueceram o
wraps—__name__e docstring errados, confusão nos logs. - Parênteses na fábrica —
@repeatem vez de@repeat(3): a função cai no parâmetrotimes. - Código pesado no corpo do decorador — ele roda na importação do módulo, e não na chamada.
Resumo
Decoradores em Python são funções de ordem superior: recebem uma função, devolvem um invólucro, e o @ é apenas uma forma curta de atribuição. Comece pelo modelo com *args, **kwargs, return result e functools.wraps, e para configurações acrescente um nível de fábrica. Funções, closures e anotações, sobre os quais se apoiam os decoradores, são praticados no curso de Python.
Perguntas frequentes
Dá para remover um decorador de uma função?
Se o decorador usa functools.wraps, o original fica disponível pelo atributo __wrapped__: func.__wrapped__(...) chama a função sem o invólucro. Caso contrário, resta apenas tirar a linha com @ do código.
Quando o código do decorador é executado?
O corpo do próprio decorador roda uma vez, na declaração da função, geralmente durante a importação do módulo. O código dentro de wrapper roda a cada chamada da função decorada.
Dá para decorar métodos de classe?
Sim, um decorador comum com *args, **kwargs funciona também com métodos: o self simplesmente será o primeiro elemento de args.
Em que um decorador difere da herança?
A herança altera o comportamento de uma classe por meio de uma hierarquia, enquanto o decorador envolve de fora uma função ou classe específica e combina-se facilmente com outros decoradores, sem mudar o código.
Este tema é abordado em profundidade, com prática, no curso “Python 3.15: от новичка до профи”.
Ir para o curso