Explicação dos decoradores Python: de @property para tentar novamente a lógica em 15 minutos (2026)
Decoradores são o recurso que separa "conhece Python" de "lê Python fluentemente". Eles parecem mágicos, mas são mecânicos: @decorator em uma função é exatamente f = decorator(f). É isso. Todo o resto neste tutorial é aquela linha aplicada em diferentes formas.
A teoria de uma linha
Quando você escreve:
Python o transforma em:
Essa é a mecânica inteira. @decorator_name acima de def executa def_name = decorator_name(def_name) após a definição. Se você internalizar isso, os decoradores deixam de ser misteriosos para sempre.
Escrevendo seu primeiro decorador (60 segundos)
Um decorador é qualquer callable que recebe uma função e retorna uma função. A forma típica:
O *args, **kwargs no wrapper é um padrão defensivo - permite que o decorador trabalhe em qualquer função, independentemente da assinatura. Ignore-o e você obterá TypeError: wrapper() takes 0 positional arguments but 2 were given no momento em que alguém decora uma função com parâmetros.
A correção @functools.wraps
Um bug sutil: depois de decorar, add.__name__ é "wrapper", não "add". Documentos e dicas de tipo também desaparecem. A correção é uma importação:
Regra: todo decorador que você escreve deve usar `@functools.wraps(fn)`. Custa uma linha e evita uma categoria inteira de problemas de depuração (introspecção, fixtures pytest, formatadores logging, pickle - todos quebram silenciosamente sem ele).
Os quatro decoradores que você verá em cada base de código
1. @property — atributos computados
@property torna um método chamável como se fosse um atributo. Perfeito para valores derivados somente leitura. Há também @area.setter para o caminho de gravação, mas 90% dos usos de @property são somente leitura.
2. @staticmethod e @classmethod
@classmethod obtém cls (a própria classe) e é usado para construtores alternativos. @staticmethod não tem nada de especial e é uma função simples que reside dentro do namespace da classe.
3. @functools.cache — memorização
Um dos decoradores stdlib mais poderosos. Memoriza automaticamente a função com base em seus argumentos. Requer que todos os argumentos sejam hasháveis (sem listas/dictos como argumentos - use tuplas).
4. @dataclasses.dataclass — eliminação padrão
@dataclass lê as anotações de tipo em nível de classe e gera __init__, __repr__, __eq__ e (com frozen=True) __hash__ automaticamente. É a maior melhoria de qualidade de vida no Python 3.7+.
Escrevendo um decorador @retry (exemplo do mundo real)
Um decorador que tenta novamente uma função em caso de falha com espera exponencial:
Observe as três funções aninhadas: retry aceita os argumentos de configuração e retorna decorator; decorator aceita a função e retorna wrapper; wrapper executa o loop de nova tentativa. Este padrão de três camadas é a aparência de qualquer decorador com argumentos - memorize a forma, não o exemplo específico.
Decoradores baseados em classes (raros, mas úteis)
Um decorador pode ser chamado por qualquer pessoa. Aulas com __call__ também funcionam:
Use quando o decorador precisar manter um estado que sobreviva a uma única chamada (um contador, um cache, um intervalo de limite de taxa).
Armadilhas comuns
1. Esquecendo `@functools.wraps` — a introspecção é interrompida silenciosamente.
2. Argumentos padrão mutáveis no próprio decorador — o bug clássico def wrapper(x, cache=[]) aparece aqui também.
3. Decorar métodos sem aceitar `self` — lembre-se que self é apenas um argumento posicional; *args, **kwargs pega bem.
4. A ordem é importante ao empilhar: @a\n@b\ndef f() é f = a(b(f)). Leia as pilhas de baixo para cima.
Prática
Escreva um decorador @timing que imprima quanto tempo a função encapsulada leva para ser executada e, em seguida, aplique-o a uma função que calcula sum(range(10_000_000)). Todo o decorador tem 8 linhas com functools.wraps + time.perf_counter.
Depois de internalizar que @x é f = x(f), os decoradores se tornam um dos recursos mais produtivos do Python. Eles estão por toda parte em bases de código reais: rotas Flask/FastAPI (@app.route), fixtures pytest (@pytest.fixture), visualizações Django (@login_required), validadores Pydantic (@field_validator). Aprender a escrevê-los sozinho desbloqueia a capacidade de ler cada um deles.
Próxima etapa: 10 aulas práticas de decorador — crie @retry, @memoise, @rate_limit e @requires_auth do zero com Python real rodando em seu navegador.