O decorador @property é uma das ferramentas mais elegantes do Python para encapsulamento e controle de acesso a atributos. Ele permite transformar métodos em atributos que podem ser acessados diretamente, sem parênteses, enquanto mantém toda a lógica de validação e computação interna. Se você busca escrever código Python mais limpo e profissional, dominar o @property é essencial.

Neste guia completo, você aprenderá desde o básico até técnicas avançadas de propiedades em Python, com exemplos práticos testados e boas práticas de mercado.

O Problema do Acesso Direto a Atributos

Em muitas linguagens orientadas a objetos, como Java e C++, o encapsulamento é feito com atributos privados e métodos getters e setters públicos. Em Python, a filosofia é diferente: tudo é público por padrão. Isso pode levar a problemas quando um atributo precisa de validação ou lógica adicional no futuro.

class ContaBancaria:
    def __init__(self, titular, saldo):
        self.titular = titular
        self.saldo = saldo

Uso direto -- sem nenhuma proteção

conta = ContaBancaria("João", 1000) conta.saldo = -500 # Saldo negativo sem aviso!

O problema acima é claro: qualquer um pode definir saldo como negativo, algo que uma conta bancária real jamais deveria permitir. Poderíamos resolver isso com métodos getter e setter tradicionais, mas a sintaxe ficaria mais verbosa e menos intuitiva.

Criando Sua Primeira Property com @property

O decorador @property transforma um método em uma propriedade que pode ser acessada como um atributo comum. Veja o exemplo:

class Temperatura:
    def __init__(self, celsius):
        self._celsius = celsius
@property
def celsius(self):
    """Retorna a temperatura em Celsius."""
    return self._celsius

@property
def fahrenheit(self):
    """Converte Celsius para Fahrenheit automaticamente."""
    return (self._celsius * 9 / 5) + 32

Uso elegante

temp = Temperatura(25) print(temp.celsius) # 25 print(temp.fahrenheit) # 77.0

Perceba como temp.celsius e temp.fahrenheit são acessados como atributos, sem parênteses. Internamente, porém, são métodos que podem conter qualquer lógica. Isso é o poder do @property: acessar com sintaxe de atributo, comportar-se como método.

Segundo a documentação oficial do Python, a função property() retorna um atributo de propriedade e pode receber até quatro argumentos: fget, fset, fdel e doc. O decorador @property é apenas açúcar sintático para usar property(fget).

Adicionando Validação com @property.setter

O @property.setter permite definir um método setter para a propriedade, executando lógica de validação sempre que o valor for alterado:

class ContaBancaria:
    def __init__(self, titular, saldo=0):
        self.titular = titular
        self._saldo = saldo
@property
def saldo(self):
    return self._saldo

@saldo.setter
def saldo(self, valor):
    if valor < 0:
        raise ValueError("Saldo não pode ser negativo!")
    self._saldo = valor

def depositar(self, quantia):
    if quantia <= 0:
        raise ValueError("Quantia deve ser positiva!")
    self.saldo += quantia  # Usa o setter automaticamente

def sacar(self, quantia):
    if quantia <= 0:
        raise ValueError("Quantia deve ser positiva!")
    if quantia > self.saldo:
        raise ValueError("Saldo insuficiente!")
    self.saldo -= quantia  # Usa o setter automaticamente

Uso seguro

conta = ContaBancaria("Maria", 1000) conta.depositar(500) print(conta.saldo) # 1500 conta.sacar(200) print(conta.saldo) # 1300

conta.saldo = -100 # ValueError!

O setter é chamado automaticamente quando atribuímos um valor à propriedade. Isso inclui atribuições dentro de outros métodos da classe, como depositar e sacar. Toda a lógica de validação fica centralizada no setter, evitando duplicação de código.

O tutorial da Real Python sobre @property demonstra como essa abordagem simplifica a manutenção de código e previne erros em produção.

Usando @property.deleter

O decorador @property.deleter define o comportamento quando usamos del em uma propriedade. É útil para liberar recursos ou resetar valores:

class Sessao:
    def __init__(self, usuario):
        self._usuario = usuario
        self._tokens = ["token123"]
@property
def usuario(self):
    return self._usuario

@property
def tokens(self):
    return self._tokens

@tokens.deleter
def tokens(self):
    print("Limpando tokens da sessão...")
    self._tokens = []

sessao = Sessao("admin") print(sessao.tokens) # ['token123'] del sessao.tokens print(sessao.tokens) # []

O deleter permite executar código de limpeza quando um atributo é deletado. É especialmente útil para gerenciar recursos como conexões de banco de dados, arquivos abertos ou caches.

Propriedades Computadas (Atributos Derivados)

Propriedades computadas são aquelas cujo valor é calculado dinamicamente a partir de outros atributos. Elas não precisam de um setter se forem apenas leitura:

class Retangulo:
    def __init__(self, largura, altura):
        self.largura = largura
        self.altura = altura
@property
def area(self):
    return self.largura * self.altura

@property
def perimetro(self):
    return 2 * (self.largura + self.altura)

@property
def diagonal(self):
    return (self.largura ** 2 + self.altura ** 2) ** 0.5

r = Retangulo(10, 5) print(r.area) # 50 print(r.perimetro) # 30 print(f"{r.diagonal:.2f}") # 11.18

Se a largura mudar, as propriedades refletem a mudança

r.largura = 20 print(r.area) # 100 (calculado dinamicamente)

Propriedades computadas são uma excelente alternativa a métodos como get_area(), get_perimetro(). A sintaxe de atributo torna o código mais natural e legível. O guia de Descritores em Python explica em detalhes como o mecanismo de property funciona internamente.

Este conceito é amplamente usado em POO. Se você está estudando Programação Orientada a Objetos em Python, entender properties é fundamental para aplicar encapsulamento corretamente.

Property vs Getters e Setters Tradicionais

Em linguagens como Java, é comum ver:

// Java
public class Pessoa {
    private String nome;
public String getNome() {
    return nome;
}

public void setNome(String nome) {
    this.nome = nome;
}

}

pessoa.getNome(); pessoa.setNome("João");

Em Python, o mesmo padrão com @property fica mais limpo:

class Pessoa:
    def __init__(self, nome):
        self._nome = nome
@property
def nome(self):
    return self._nome

@nome.setter
def nome(self, valor):
    if not valor.strip():
        raise ValueError("Nome não pode ser vazio!")
    self._nome = valor.strip()

@nome.deleter
def nome(self):
    print("Deletando nome...")
    self._nome = None

p = Pessoa(" Maria ") print(p.nome) # Maria (acesso direto) p.nome = "Ana" print(p.nome) # Ana

A diferença fundamental é que o código cliente não precisa saber se está acessando um atributo ou um método. Isso permite começar com um atributo simples e, se no futuro for necessário adicionar validação, basta transformá-lo em property sem quebrar a API pública. Esse princípio é conhecido como PEP 8 -- Style Guide for Python Code, que recomenda o uso de properties para manter a consistência.

Encapsulamento e Validação com Setters Inteligentes

Um dos casos de uso mais comuns para @property é a validação de dados no setter. Vamos criar uma classe Usuario com validações robustas:

import re

class Usuario: def init(self, email, idade): self.email = email # Usa o setter na inicialização self.idade = idade # Usa o setter na inicialização self._ativo = True

@property
def email(self):
    return self._email

@email.setter
def email(self, valor):
    if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', valor):
        raise ValueError("Email inválido!")
    self._email = valor.lower()

@property
def idade(self):
    return self._idade

@idade.setter
def idade(self, valor):
    if not isinstance(valor, int):
        raise TypeError("Idade deve ser um número inteiro!")
    if valor < 0 or valor > 150:
        raise ValueError("Idade deve estar entre 0 e 150!")
    self._idade = valor

@property
def ativo(self):
    return self._ativo

Uso com validação automática

user = Usuario("[email protected]", 25) print(user.email) # [email protected] (convertido para minúsculas) print(user.idade) # 25

user.email = "invalido" # ValueError!

user.idade = 200 # ValueError!

Perceba como o construtor também passa pelos setters, garantindo que mesmo objetos recém-criados tenham dados válidos. O site Stack Overflow explica detalhadamente o funcionamento do decorador property e suas nuances.

Propriedades com Cache (Lazy Evaluation)

Quando uma propriedade computada é cara de calcular, podemos usar cache para armazenar o resultado e recalcular apenas quando necessário:

class Relatorio:
    def __init__(self, dados):
        self.dados = dados
        self._cache = {}
@property
def analise_completa(self):
    if "analise" not in self._cache:
        print("Calculando análise completa... (operação cara)")
        resultado = {
            "total": sum(self.dados),
            "media": sum(self.dados) / len(self.dados),
            "maximo": max(self.dados),
            "minimo": min(self.dados),
            "tamanho": len(self.dados)
        }
        self._cache["analise"] = resultado
    return self._cache["analise"]

def invalidar_cache(self):
    self._cache = {}

rel = Relatorio([10, 20, 30, 40, 50]) print(rel.analise_completa["media"]) # Calcula na primeira vez print(rel.analise_completa["total"]) # Usa cache na segunda vez rel.invalidar_cache() print(rel.analise_completa) # Recalcula

Esse padrão é conhecido como lazy evaluation e é especialmente útil em operações de I/O, consultas a bancos de dados ou processamento intensivo de dados, como demonstrado no The Hitchhiker's Guide to Python.

Herança e Properties

Properties se comportam como métodos na herança: podem ser sobrescritas em subclasses para estender ou modificar o comportamento:

class Animal:
    def __init__(self, nome):
        self._nome = nome
@property
def nome(self):
    return self._nome

@property
def som(self):
    return "..."

class Cachorro(Animal): @property def som(self): return "Au au!"

class Gato(Animal): @property def som(self): return "Miau!"

class Papagaio(Animal): def init(self, nome): super().init(nome) self._palavras = []

def aprender(self, palavra):
    self._palavras.append(palavra)

@property
def som(self):
    if self._palavras:
        return ", ".join(self._palavras)
    return "..."

animais = [Cachorro("Rex"), Gato("Mimi"), Papagaio("Louro")] for animal in animais: if isinstance(animal, Papagaio): animal.aprender("Olá!") animal.aprender("Java é chato") print(f"{animal.nome}: {animal.som}")

A sobrescrita de properties funciona como a sobrescrita de métodos convencionais. Você pode redeclarar o @property na subclasse ou usar super() para estender a implementação original. O guia da GeeksforGeeks sobre @property traz exemplos adicionais de herança com properties.

Properties como Atributos Somente Leitura

Para criar atributos que podem ser lidos mas não modificados depois da criação, basta definir apenas o getter sem o setter:

class Configuracao:
    def __init__(self):
        self._versao = "2.0.1"
        self._data_criacao = "2026-01-15"
@property
def versao(self):
    return self._versao

@property
def data_criacao(self):
    return self._data_criacao

config = Configuracao() print(config.versao) # 2.0.1 print(config.data_criacao) # 2026-01-15

config.versao = "3.0.0" # AttributeError!

Properties somente leitura são ideais para expor metadados, constantes de configuração ou informações de sistema sem risco de alteração acidental. Para um guia mais aprofundado sobre encapsulamento, veja nosso artigo sobre Decorators em Python, que mostra o uso avançado de decorators como @property.

Property com Type Hints

O Python moderno suporta type hints em properties, melhorando a legibilidade e permitindo checagem estática com ferramentas como mypy:

class Produto:
    def __init__(self, nome: str, preco: float) -> None:
        self._nome = nome
        self._preco = preco
@property
def nome(self) -> str:
    return self._nome

@nome.setter
def nome(self, valor: str) -> None:
    if not valor.strip():
        raise ValueError("Nome não pode ser vazio!")
    self._nome = valor.strip()

@property
def preco(self) -> float:
    return self._preco

@preco.setter
def preco(self, valor: float) -> None:
    if valor < 0:
        raise ValueError("Preço não pode ser negativo!")
    self._preco = round(valor, 2)

@property
def preco_com_imposto(self) -> float:
    """Retorna o preço com 10% de imposto."""
    return round(self._preco * 1.1, 2)

p = Produto("Notebook", 3500.00) print(f"{p.nome}: R$ {p.preco:.2f} (com imposto: R$ {p.preco_com_imposto:.2f})")

Type hints em properties ajudam IDEs a fornecer autocomplete e detectar erros em tempo de desenvolvimento, além de servirem como documentação viva do código. Saiba mais sobre type hints no módulo typing da documentação oficial.

Quando Evitar @property

Apesar de poderoso, o uso excessivo de properties pode tornar o código confuso. Evite usar @property quando:

  • O método realiza uma operação cara que não deve ser enganosamente rápida (prefira um método explícito como .calcular_relatorio())
  • O método tem efeitos colaterais significativos (properties devem ser seguras e previsíveis)
  • O método aceita parâmetros (properties não aceitam argumentos além de self)
  • O acesso à propriedade lança exceções frequentes (isso quebra a expectativa de que atributos são seguros)
# RUIM: operação cara disfarçada de atributo
class BancoDados:
    @property
    def todos_usuarios(self):
        # Query SQL pesada -- melhor como método explícito
        return self._executar_query("SELECT * FROM usuarios")

BOM: método explícito para operações pesadas

class BancoDados: def listar_usuarios(self): return self._executar_query("SELECT * FROM usuarios")

O princípio é simples: properties devem ser "baratas", previsíveis e sem efeitos colaterais. Para operações custosas ou com parâmetros, use métodos convencionais.

Conclusão

O decorador @property é uma ferramenta indispensável no kit do desenvolvedor Python. Ele permite escrever código limpo, seguro e que segue o princípio do encapsulamento sem sacrificar a simplicidade. Com ele, você pode começar com atributos públicos simples e, conforme a necessidade, adicionar validação e lógica sem quebrar a API pública da sua classe.

Dominar @property é um passo importante para escrever código Python profissional. Quando combinado com type hints, herança e boas práticas de validação, você cria classes robustas, testáveis e fáceis de manter.

Continue seus estudos: explore nosso guia completo de Magic Methods em Python para aprofundar ainda mais seus conhecimentos em POO com Python.