Decorators em Python: Entenda @property, @classmethod e @staticmethod
Você já se perguntou o que são @property, @classmethod e @staticmethod em Python? Este guia completo desmistifica os decorators, explicando desde o conceito fundamental de funções como cidadãs de primeira classe até a criação de decorators customizados. Entenda a sintaxe '@', veja exemplos práticos e aprenda a usar os decorators de OOP para escrever código elegante e reutilizável.

Série OOP em Python — Parte 2
Parte 1 — OOP em Python: do zero, de verdade
Você já viu @property, @staticmethod e ficou com aquela cara de interrogação? Antes de entender esses três, a gente precisa entender o que um decorator é de verdade.
Primeiro: funções são cidadãs de primeira classe
Em Python, funções são objetos como qualquer outro. Isso significa que você pode passar funções como argumento, retornar funções e guardar funções em variáveis.
def saudacao(): print("Olá, mundo!")# Guardando a função numa variável (sem os parênteses!)minha_fn = saudacaominha_fn() # Olá, mundo!# Passando função como argumentodef executar(fn): print("Antes...") fn() print("Depois.")executar(saudacao)# Antes...# Olá, mundo!# Depois.💡 Por que isso importa? Um decorator nada mais é do que uma função que recebe outra função, adiciona algum comportamento, e retorna uma nova função. É exatamente isso.
Construindo um decorator do zero
Vamos criar um decorator que mede o tempo de execução de qualquer função. Útil pra debug e otimização.
import timedef medir_tempo(funcao): # funcao é a função que vamos "decorar" def wrapper(*args, **kwargs): inicio = time.time() resultado = funcao(*args, **kwargs) # executa a função original fim = time.time() print(f"⏱️ {funcao.__name__} levou {fim - inicio:.4f}s") return resultado return wrapper # retorna a função "embrulhada"# Sem a sintaxe @, seria assim:def processar_dados(): time.sleep(0.5) print("Dados processados!")processar_dados = medir_tempo(processar_dados) # manual e feioprocessar_dados()🧠 Modelo mental: o decorator é um embrulho. A função original entra, o decorator coloca ela dentro de uma caixa com comportamentos extras, e devolve a caixa.
O açúcar sintático: o @
O @ é só uma forma mais bonita de escrever exatamente o que fizemos acima. As duas versões são idênticas:
# Com @ — a forma que você vai usar sempre@medir_tempodef processar_dados(): time.sleep(0.5) print("Dados processados!")processar_dados()# Dados processados!# ⏱️ processar_dados levou 0.5003s# Funciona em qualquer função!@medir_tempodef buscar_usuario(id): time.sleep(0.1) return {"id": id, "nome": "Genildo"}usuario = buscar_usuario(42)# ⏱️ buscar_usuario levou 0.1001s✨ Sacou a mágica? Você escreveu a lógica de medir tempo uma vez só e aplicou em qualquer função com um
@. Isso é o princípio DRY (Don't Repeat Yourself) em ação.
Agora sim: decorators de OOP
Python tem três decorators nativos pra classes que você vai ver em todo lugar:
Decorator | O que faz |
|---|---|
| Método que parece atributo |
| Método da classe, não do objeto |
| Função independente dentro da classe |
@property — acesso controlado com elegância
Lembra do encapsulamento com __saldo na Parte 1? O @property permite que você acesse dados protegidos como se fossem atributos simples, mas com lógica por baixo.
class Produto: def __init__(self, nome, preco): self.nome = nome self.__preco = preco # privado @property def preco(self): # getter: chamado ao ler produto.preco return f"R$ {self.__preco:.2f}" @preco.setter def preco(self, valor): # setter: chamado ao atribuir produto.preco = X if valor < 0: raise ValueError("Preço não pode ser negativo! 🚫") self.__preco = valorcamiseta = Produto("Camiseta Blueprint", 89.90)print(camiseta.preco) # R$ 89.90 — parece atributo, mas é método!camiseta.preco = 79.90 # usa o setter automaticamenteprint(camiseta.preco) # R$ 79.90camiseta.preco = -10 # ValueError: Preço não pode ser negativo! 🚫🔶 Quando usar @property? Quando você quer que o acesso a um dado tenha lógica embutida — formatação, validação, cálculo derivado — mas a API externa continue limpa, sem
get_preco()eset_preco().
@classmethod — construindo objetos de formas alternativas
Um @classmethod recebe a classe como primeiro argumento (convencionalmente cls), não o objeto. É muito usado pra criar factory methods — formas alternativas de instanciar uma classe.
class Artigo: def __init__(self, titulo, autor, categoria): self.titulo = titulo self.autor = autor self.categoria = categoria @classmethod def de_dicionario(cls, dados: dict): # cria um Artigo a partir de um dict (ex: resposta de API) return cls( titulo=dados["title"], autor=dados["author"], categoria=dados["category"] ) @classmethod def rascunho(cls, titulo): # cria um artigo rascunho com defaults return cls(titulo=titulo, autor="Desconhecido", categoria="Rascunho") def __repr__(self): return f"[{self.categoria}] {self.titulo} — {self.autor}"# Forma normala1 = Artigo("OOP em Python", "Genildo", "Python")# Via dicionário (vindo de uma API, por exemplo)payload = {"title": "Decorators", "author": "Genildo", "category": "Python"}a2 = Artigo.de_dicionario(payload)# Rascunho rápidoa3 = Artigo.rascunho("Ideia de artigo novo")print(a2) # [Python] Decorators — Genildoprint(a3) # [Rascunho] Ideia de artigo novo — Desconhecido@staticmethod — utilidade sem estado
Um @staticmethod não recebe self nem cls. É uma função comum que mora dentro da classe por organização lógica, não por acoplamento.
class ValidadorSenha: @staticmethod def tem_tamanho_minimo(senha: str) -> bool: return len(senha) >= 8 @staticmethod def tem_numero(senha: str) -> bool: return any(c.isdigit() for c in senha) @staticmethod def validar(senha: str) -> bool: return ( ValidadorSenha.tem_tamanho_minimo(senha) and ValidadorSenha.tem_numero(senha) )print(ValidadorSenha.validar("abc")) # False — curta e sem númeroprint(ValidadorSenha.validar("blueprint1")) # True ✅⚠️ Regra rápida pra não confundir:
Precisa acessar
self(dados do objeto)? → método normalPrecisa acessar
cls(a classe em si)? →@classmethodNão precisa de nenhum dos dois? →
@staticmethod
Decorator customizado + OOP juntos
Pra fechar com chave de ouro: um decorator que loga automaticamente toda chamada de método numa classe. Esse padrão aparece em frameworks de verdade.
def logar_chamada(funcao): def wrapper(*args, **kwargs): print(f"📋 Chamando: {funcao.__name__}()") resultado = funcao(*args, **kwargs) print(f"✅ Concluído: {funcao.__name__}()") return resultado return wrapperclass ServicoDePost: def __init__(self): self.__posts = [] @logar_chamada def criar_post(self, titulo): self.__posts.append(titulo) return titulo @logar_chamada def publicar_todos(self): for post in self.__posts: print(f" 🚀 Publicando: {post}") @property def total(self): return len(self.__posts) @staticmethod def slug(titulo: str) -> str: return titulo.lower().replace(" ", "-")servico = ServicoDePost()servico.criar_post("OOP em Python")servico.criar_post("Decorators em Python")servico.publicar_todos()print(f"Total: {servico.total}")print(ServicoDePost.slug("Decorators em Python"))# decorators-em-pythonO que você aprendeu hoje
✅ Funções são objetos em Python — podem ser passadas e retornadas.
✅ Um decorator é uma função que embrulha outra função.
✅ O
@é açúcar sintático — não tem mágica, só elegância.✅
@propertycria acesso controlado a atributos privados.✅
@classmethodopera na classe, não no objeto — ótimo pra factory methods.✅
@staticmethodé uma função utilitária que mora na classe por organização.✅ Decorators customizados + OOP = código limpo e reutilizável.


