O que é @property e por que usar
Em Python, @property permite expor um método como se fosse um atributo. Isso cria uma interface de acesso simples (leitura/escrita) enquanto você mantém lógica interna (cálculos, validação, compatibilidade com versões anteriores) sem obrigar quem usa sua classe a chamar métodos do tipo get_* e set_*.
Uma propriedade pode ter até três partes:
- getter: executa quando você lê o atributo (
obj.atributo). - setter: executa quando você atribui (
obj.atributo = valor). - deleter: executa quando você remove (
del obj.atributo).
O objetivo é oferecer uma API legível e estável: acesso por atributo por fora, regras e consistência por dentro.
Passo a passo: criando uma propriedade com getter e setter
1) Comece com um atributo interno (backing field)
Uma prática comum é armazenar o valor real em um atributo interno (por exemplo, _preco) e expor uma propriedade pública (preco) que controla o acesso.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self._preco = None
self.preco = preco # passa pelo setter
@property
def preco(self):
return self._preco
@preco.setter
def preco(self, valor):
if not isinstance(valor, (int, float)):
raise TypeError("preco deve ser numérico")
if valor < 0:
raise ValueError("preco não pode ser negativo")
self._preco = float(valor)2) Use como atributo, mas com validação
p = Produto("Caderno", 12.5)
print(p.preco) # 12.5
p.preco = 20 # ok
p.preco = -1 # ValueError
p.preco = "caro" # TypeErrorNote que quem consome a classe não precisa saber que existe validação: a interface continua sendo um atributo simples.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Atributos computados: propriedades que calculam valores
Propriedades também são ótimas para atributos que não precisam ser armazenados, pois podem ser calculados sob demanda. Isso evita duplicação de estado e reduz risco de inconsistência.
Exemplo: total de um item (preço × quantidade)
class ItemCarrinho:
def __init__(self, nome, preco_unitario, quantidade):
self.nome = nome
self.preco_unitario = float(preco_unitario)
self.quantidade = int(quantidade)
@property
def total(self):
return self.preco_unitario * self.quantidadeitem = ItemCarrinho("Caneta", 2.5, 4)
print(item.total) # 10.0total não é armazenado; ele sempre reflete o estado atual de preco_unitario e quantidade.
Exemplo: preço com desconto (regra de negócio)
Uma propriedade computada pode aplicar regras, como desconto percentual limitado entre 0 e 100.
class ProdutoComDesconto:
def __init__(self, nome, preco, desconto_percentual=0):
self.nome = nome
self.preco = float(preco)
self.desconto_percentual = desconto_percentual
@property
def desconto_percentual(self):
return self._desconto_percentual
@desconto_percentual.setter
def desconto_percentual(self, valor):
if not isinstance(valor, (int, float)):
raise TypeError("desconto_percentual deve ser numérico")
if valor < 0 or valor > 100:
raise ValueError("desconto_percentual deve estar entre 0 e 100")
self._desconto_percentual = float(valor)
@property
def preco_com_desconto(self):
return self.preco * (1 - self.desconto_percentual / 100)p = ProdutoComDesconto("Livro", 100, 10)
print(p.preco_com_desconto) # 90.0
p.desconto_percentual = 25
print(p.preco_com_desconto) # 75.0Quando usar setter e quando evitar
Use setter quando:
- Você precisa validar valores (faixa, tipo, formato).
- Você precisa normalizar dados (ex.: converter para
float, padronizar string). - Você precisa manter invariantes (ex.: quantidade não negativa).
Evite setter quando:
- O atributo deve ser somente leitura (ex.:
idimutável). - Alterações exigem um método explícito por clareza (ex.:
aprovar(),cancelar()).
Propriedade somente leitura (sem setter)
Se você definir apenas o getter, a propriedade vira somente leitura. Isso é útil para valores calculados ou para campos que não devem ser alterados diretamente.
class Retangulo:
def __init__(self, largura, altura):
self.largura = float(largura)
self.altura = float(altura)
@property
def area(self):
return self.largura * self.alturar = Retangulo(3, 4)
print(r.area) # 12.0
r.area = 10 # AttributeErrorDeleter: controlando remoção de atributos
O deleter é menos comum, mas pode ser útil quando “apagar” um atributo precisa executar alguma lógica: limpar cache, desfazer vínculo, ou impedir remoção indevida.
class Usuario:
def __init__(self, email):
self._email = None
self.email = email
@property
def email(self):
return self._email
@email.setter
def email(self, valor):
if "@" not in valor:
raise ValueError("email inválido")
self._email = valor.strip().lower()
@email.deleter
def email(self):
raise AttributeError("email não pode ser removido")u = Usuario("Pessoa@Exemplo.com")
print(u.email) # pessoa@exemplo.com
del u.email # AttributeErrorMigrando de atributo público para propriedade sem quebrar código
Um uso muito prático de @property é evoluir uma classe. Imagine que, no início, você expôs um atributo público (preco) e vários lugares do sistema fazem obj.preco e obj.preco = .... Depois, você percebe que precisa validar e normalizar. Se você trocar para set_preco(), quebrará o código consumidor. Com propriedade, você mantém a mesma interface.
Cenário inicial (sem validação)
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = precoO consumidor faz:
p = Produto("Mouse", 50)
p.preco = -10 # passava sem impedirEvolução compatível (com propriedade)
Você passa a armazenar em _preco e cria a propriedade preco. Quem usa continua acessando preco do mesmo jeito.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self._preco = None
self.preco = preco
@property
def preco(self):
return self._preco
@preco.setter
def preco(self, valor):
if valor < 0:
raise ValueError("preco não pode ser negativo")
self._preco = float(valor)O código consumidor permanece:
p = Produto("Mouse", 50)
print(p.preco)
p.preco = 60Mas agora valores inválidos são bloqueados.
Compatibilidade extra: mantendo métodos antigos (opcional)
Se seu código antigo usava get_preco() e set_preco(), você pode mantê-los como “atalhos” chamando a propriedade, facilitando uma migração gradual.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self._preco = None
self.preco = preco
@property
def preco(self):
return self._preco
@preco.setter
def preco(self, valor):
if valor < 0:
raise ValueError("preco não pode ser negativo")
self._preco = float(valor)
# compatibilidade
def get_preco(self):
return self.preco
def set_preco(self, valor):
self.preco = valorBoas práticas ao trabalhar com propriedades
- Evite efeitos colaterais inesperados no getter: getters devem ser rápidos e previsíveis. Se o cálculo for pesado, considere cache explícito.
- Nomeie o atributo interno de forma consistente:
_precopara a propriedadepreco. - Valide no setter e normalize: converta tipos e aplique regras no ponto de entrada.
- Prefira propriedades para “atributos” e métodos para “ações” (ex.:
fechar_pedido()). - Não duplique estado: se algo pode ser derivado (ex.:
total), prefira propriedade computada a armazenar e atualizar manualmente.
Exercícios práticos
1) Transformar get/set em propriedades
Você recebeu uma classe com métodos de acesso. Reescreva usando @property e mantenha a mesma regra de validação.
class Conta:
def __init__(self, saldo):
self._saldo = saldo
def get_saldo(self):
return self._saldo
def set_saldo(self, valor):
if valor < 0:
raise ValueError("saldo não pode ser negativo")
self._saldo = valor- Crie a propriedade
saldocom getter e setter. - Faça
__init__atribuir viaself.saldo = saldopara reutilizar validação.
2) Implementar atributo computado com regra de negócio
Crie uma classe Pedido com:
itens(lista de tuplas(preco_unitario, quantidade)ou uma estrutura equivalente).- Propriedade computada
totalque soma todos os itens. - Propriedade
cupom_percentualcom validação (0 a 30, por exemplo). - Propriedade computada
total_com_descontoaplicando o cupom.
3) Migrar atributo público para propriedade sem quebrar uso
Considere que o sistema já usa produto.estoque diretamente. Você precisa impedir estoque negativo e garantir inteiro.
- Implemente
estoquecomo propriedade com backing field_estoque. - Garanta que
produto.estoque = 10continue funcionando. - Bloqueie
produto.estoque = -1eproduto.estoque = 2.5(ou normalize para inteiro, conforme sua regra).
4) Usar deleter para proteger um dado sensível
Crie uma classe APIConfig com propriedade token:
- Setter exige string não vazia.
- Getter retorna o token.
- Deleter deve impedir remoção (levantar
AttributeError) ou, alternativamente, limpar o token e registrar uma marca interna_revogado = True(defina a regra e implemente).
5) Refatoração guiada: evitar estado duplicado
Você tem uma classe que armazena subtotal e também armazena preco e quantidade, causando inconsistências. Refatore para:
- Remover o armazenamento de
subtotal. - Criar propriedade computada
subtotalbaseada emprecoequantidade. - Adicionar validação em
precoequantidadevia propriedades.
| Objetivo | O que praticar |
|---|---|
| Interface simples | Acesso por atributo com lógica interna |
| Consistência | Validação e normalização no setter |
| Menos bugs | Propriedades computadas para evitar estado duplicado |
| Evolução sem quebra | Migração de atributo público para propriedade |