Code & DevelopmentCode & Development · 18 Jun 2026
Python Decorators: Understanding @property, @classmethod, and @staticmethod
Confused by Python decorators? This guide demystifies @property, @classmethod, and @staticmethod, showing you how to write cleaner, modular code.
Mastering Python Decorators
Decorators are functions that wrap other functions to add behavior, serving as a powerful tool for code reuse and the DRY principle.
The @property decorator allows methods to be accessed like attributes, enabling clean syntax for data validation and encapsulation.
Use @classmethod to define factory methods that receive the class itself as an argument, providing flexible ways to instantiate objects.
The @staticmethod decorator creates utility functions within a class that do not require access to instance or class state.
Syntactic sugar with the @ symbol simplifies the application of decorators, making code more readable and easier to maintain.
Have you ever seen @property, @staticmethod and felt that look of confusion? Before understanding these three, we need to understand what a decorator is for real.
First: functions are first-class citizens
In Python, functions are objects like any other. This means you can pass functions as arguments, return functions and store functions in variables.
def saudacao():
print("Olá, mundo!")
# Guardando a função numa variável (sem os parênteses!)
minha_fn = saudacao
minha_fn() # Olá, mundo!
# Passando função como argumento
def executar(fn):
print("Antes...")
fn()
print("Depois.")
executar(saudacao)
# Antes...
# Olá, mundo!
# Depois.
💡 Why does this matter? A decorator is nothing more than a function that receives another function, adds some behavior, and returns a new function. That is exactly it.
Building a decorator from scratch
Let's create a decorator that measures the execution time of any function. Useful for debugging and optimization.
import time
def 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 feio
processar_dados()
🧠 Mental model: the decorator is a wrapper. The original function goes in, the decorator puts it inside a box with extra behaviors, and returns the box.
Syntactic sugar: the @
The @ is just a prettier way to write exactly what we did above. Both versions are identical:
# Com @ — a forma que você vai usar sempre
@medir_tempo
def processar_dados():
time.sleep(0.5)
print("Dados processados!")
processar_dados()
# Dados processados!
# ⏱️ processar_dados levou 0.5003s
# Funciona em qualquer função!
@medir_tempo
def buscar_usuario(id):
time.sleep(0.1)
return {"id": id, "nome": "Genildo"}
usuario = buscar_usuario(42)
# ⏱️ buscar_usuario levou 0.1001s
✨ Got the magic? You wrote the logic to measure time only once and applied it to any function with a
@. This is the DRY (Don't Repeat Yourself) principle in action.
Now for real: OOP decorators
Python has three native decorators for classes that you will see everywhere:
Python Native Class Decorators
Key decorators that shape class behavior and usage
@property
Attribute-like Method
- Allows methods to be accessed like attributes for controlled access.
@classmethod
Class Method
- Defines methods bound to the class, not instances.
@staticmethod
Static Utility
- Creates independent functions inside the class without access to instance or class.
Decorator | What it does |
|---|---|
| Method that looks like an attribute |
| Class method, not object method |
| Independent function inside the class |
@property — controlled access with elegance
Remember encapsulation with __saldo in Part 1? The @property allows you to access protected data as if they were simple attributes, but with logic underneath.
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 = valor
camiseta = Produto("Camiseta Blueprint", 89.90)
print(camiseta.preco) # R$ 89.90 — parece atributo, mas é método!
camiseta.preco = 79.90 # usa o setter automaticamente
print(camiseta.preco) # R$ 79.90
camiseta.preco = -10 # ValueError: Preço não pode ser negativo! 🚫
🔶 When to use @property? When you want access to data to have built-in logic — formatting, validation, derived calculation — but the external API remains clean, without
get_preco()andset_preco().
@classmethod — building objects in alternative ways
A @classmethod receives the class as the first argument (conventionally cls), not the object. It is widely used to create factory methods — alternative ways to instantiate a class.
A @classmethod receives the class itself as the first argument (cls), not an instance. This allows it to access and modify class state or create new instances in alternative ways. It is commonly used for factory methods or alternative constructors, providing flexible object creation patterns.
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 normal
a1 = 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ápido
a3 = Artigo.rascunho("Ideia de artigo novo")
print(a2) # [Python] Decorators — Genildo
print(a3) # [Rascunho] Ideia de artigo novo — Desconhecido
@staticmethod — stateless utility
A @staticmethod does not receive self or cls. It is a common function that lives inside the class for logical organization, not for coupling.
@staticmethod methods do not receive self or cls parameters, meaning they do not interact with instance or class data.
They serve as utility functions grouped logically within a class, without maintaining state or coupling to class/instance.
Use @staticmethod when the method neither needs instance nor class context, clarifying its stateless role and avoiding confusion.
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úmero
print(ValidadorSenha.validar("blueprint1")) # True ✅
⚠️ Quick rule to avoid confusion:
Need to access
self(object data)? → normal methodNeed to access
cls(the class itself)? →@classmethodDon't need either? →
@staticmethod
Custom decorator + OOP together
To wrap it up: a decorator that automatically logs every method call in a class. This pattern appears in real-world frameworks.
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 wrapper
class 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-python
What you learned today
✅ Functions are objects in Python — they can be passed and returned.
✅ A decorator is a function that wraps another function.
✅ The
@is syntactic sugar — no magic, just elegance.✅
@propertycreates controlled access to private attributes.✅
@classmethodoperates on the class, not the object — great for factory methods.✅
@staticmethodis a utility function that lives in the class for organization.✅ Custom decorators + OOP = clean and reusable code.