Propriedades (property) em POO Python: getters, setters e atributos computados

Capítulo 5

Tempo estimado de leitura: 8 minutos

+ Exercício

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"    # TypeError

Note que quem consome a classe não precisa saber que existe validação: a interface continua sendo um atributo simples.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

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.quantidade
item = ItemCarrinho("Caneta", 2.5, 4)
print(item.total)  # 10.0

total 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.0

Quando 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.: id imutá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.altura
r = Retangulo(3, 4)
print(r.area)  # 12.0
r.area = 10    # AttributeError

Deleter: 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     # AttributeError

Migrando 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 = preco

O consumidor faz:

p = Produto("Mouse", 50)
p.preco = -10  # passava sem impedir

Evoluçã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 = 60

Mas 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 = valor

Boas 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: _preco para a propriedade preco.
  • 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 saldo com getter e setter.
  • Faça __init__ atribuir via self.saldo = saldo para 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 total que soma todos os itens.
  • Propriedade cupom_percentual com validação (0 a 30, por exemplo).
  • Propriedade computada total_com_desconto aplicando 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 estoque como propriedade com backing field _estoque.
  • Garanta que produto.estoque = 10 continue funcionando.
  • Bloqueie produto.estoque = -1 e produto.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 subtotal baseada em preco e quantidade.
  • Adicionar validação em preco e quantidade via propriedades.
ObjetivoO que praticar
Interface simplesAcesso por atributo com lógica interna
ConsistênciaValidação e normalização no setter
Menos bugsPropriedades computadas para evitar estado duplicado
Evolução sem quebraMigração de atributo público para propriedade

Agora responda o exercício sobre o conteúdo:

Qual cenário descreve melhor o benefício de usar @property para manter uma API estável ao evoluir uma classe?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Com @property, você pode adicionar validação/normalização e usar um backing field (ex.: _preco) sem mudar a forma como o código consumidor acessa o valor (obj.preco e obj.preco = ...).

Próximo capitúlo

Herança em Python Orientado a Objetos: subclasses, super() e especialização segura

Arrow Right Icon
Capa do Ebook gratuito Python Orientado a Objetos para Iniciantes: Classes, Herança e Boas Práticas
36%

Python Orientado a Objetos para Iniciantes: Classes, Herança e Boas Práticas

Novo curso

14 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.