Code & Development

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.

Python Decorators: Understanding @property, @classmethod, and @staticmethod

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.

PYTHON
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.

💡 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.

PYTHON
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()

🧠 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:

PYTHON
# 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

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
@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.

Key decorators that shape class behavior and usage

Decorator

What it does

@property

Method that looks like an attribute

@classmethod

Class method, not object method

@staticmethod

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.

@property Controlled Access to Private Attributes accessed by exposed as Private attribute __preco Holds the actual price value @property preco() method Getter and setter with logic External attribute-like access Uses preco as if attribute
@property Controlled Access to Private Attributes
PYTHON
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! 🚫

🔶 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() and set_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.

PYTHON
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 — 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.

PYTHON
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 ✅

⚠️ Quick rule to avoid confusion:

  • Need to access self (object data)? → normal method

  • Need to access cls (the class itself)? → @classmethod

  • Don'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.

PYTHON
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-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.

  • @property creates controlled access to private attributes.

  • @classmethod operates on the class, not the object — great for factory methods.

  • @staticmethod is a utility function that lives in the class for organization.

  • ✅ Custom decorators + OOP = clean and reusable code.