Guia de estudo

10/04/2026 · Aula 04

Encapsulamento

em Python

Programação Orientada a Objetos com Python
Tecnologia em Análise e Desenvolvimento de Sistemas

4 horas Teoria + Prática Proteção de dados @property

motivação

O problema do dado exposto

Quando os atributos ficam abertos, qualquer código pode alterá-los sem validação.

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

conta = ContaBancaria("Ana", 1000)

# Qualquer um pode fazer isso:
conta.saldo = -99999   # saldo negativo!
conta.saldo = "banana"  # tipo errado!
conta.titular = None    # sem titular!

print(conta.saldo)  # banana
Sem proteção, o objeto não consegue garantir que seus dados estão consistentes. Encapsulamento resolve isso.

conceito fundamental

O que é encapsulamento?

  • 🔒 Dados internos ficam protegidos de acesso externo direto
  • 👤 O objeto controla como seus dados são lidos e alterados
  • Validações garantem que o objeto nunca fica em estado inválido
  • 🔄 Você pode mudar a implementação interna sem quebrar quem usa a classe

👻 Controle remoto

Você aperta o volume. Não sabe como o sinal infravermelho funciona por dentro. O objeto expõe uma interface simples e esconde a complexidade.

🏭 Caixa eletrônico

Você saca pela tela. Não acessa o cofre direto. O banco decide o que você pode fazer e como.

nível 1 de proteção

Convenção _

Um underscore é uma convenção — um aviso para outros desenvolvedores. Python não bloqueia, mas sinaliza que o atributo é interno.

convencao.py
class ContaBancaria:
    def __init__(self, titular, saldo):
        self.titular = titular
        self._saldo  = saldo  # _ = "use os métodos, não isso"

    def depositar(self, valor):
        if valor > 0:
            self._saldo += valor

    def ver_saldo(self):
        return self._saldo

conta = ContaBancaria("Ana", 1000)
conta.depositar(500)
print(conta.ver_saldo())  # 1500

# Ainda funciona, mas é má prática:
conta._saldo = -9999
O _ é um acordo entre desenvolvedores, não uma barreira técnica.

nível 2 de proteção · herança

Duplo underscore __

O objetivo principal é evitar conflito acidental em herança, impedindo que subclasses sobrescrevam atributos vitais.

privado.py
class ContaBancaria:
    def __init__(self, titular, saldo):
        self.titular = titular
        self.__saldo = saldo  # __ ativa o name mangling

    def ver_saldo(self):
        return self.__saldo

conta = ContaBancaria("Ana", 1000)
# O acesso externo direto falha:
# print(conta.__saldo)  # AttributeError

# O Python renomeou o atributo internamente para:
print(conta._ContaBancaria__saldo)  # 1000 (evite usar!)
Nota: Em Python, encapsulamento é sobre comunicação. O __ dificulta o acesso para proteger a arquitetura da classe.

comparação

_ vs __

Na prática profissional, _ é suficiente na maioria dos casos. Use __ quando precisar garantir integridade em heranças.

_ underscore simples

  • · Convenção — sem proteção técnica
  • · Sinaliza "uso interno, cuidado"
  • · Mais comum no dia a dia
  • · Facilita debugging e testes

self._saldo = saldo

__ duplo underscore

  • · Name mangling — proteção de nome
  • · Bloqueia acesso externo direto
  • · Útil para evitar conflito em herança
  • · Menos comum, mais restritivo

self.__saldo = saldo

controlando o acesso

Getters e Setters

Antes da forma elegante do Python, entenda o conceito: métodos que leem (get) e alteram (set) atributos com validação.

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

    def get_saldo(self):          # getter
        return self._saldo

    def set_saldo(self, valor):    # setter
        if valor < 0:
            print("Erro: saldo não pode ser negativo")
            return
        self._saldo = valor

conta = ContaBancaria("Ana", 1000)
conta.set_saldo(-500)  # Erro: saldo não pode ser negativo
conta.set_saldo(1500)  # ok
print(conta.get_saldo())   # 1500
Funciona, mas é verboso. Python resolve isso de forma elegante usando @property.

a forma pythônica

O decorador @property

@property transforma um método em um atributo calculado. Você acessa como um dado comum, mas o valor é processado na hora.

atributo_calculado.py
class Circulo:
    def __init__(self, raio):
        self._raio = raio

    @property
    def area(self): # Este é o nosso "getter" pythônico!
        return 3.14159 * self._raio ** 2

c = Circulo(5)
print(c.area) # 78.53... (acesso limpo)

# Tentativa de alterar gera erro (somente leitura):
c.area = 100  # AttributeError: can't set attribute
Por padrão, @property sem um setter cria um atributo de somente leitura.

controlando a escrita

O decorador @saldo.setter

O setter permite alterar o atributo com validação automática. A sintaxe usa o nome da property mais .setter.

setter.py
class ContaBancaria:
    def __init__(self, titular, saldo):
        self.titular = titular
        self.saldo = saldo # ← Gatilho: chama o @saldo.setter!

    @property
    def saldo(self):
        return self._saldo # Note o _ interno

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

conta = ContaBancaria("Ana", 1000)
conta.saldo = -100 # Tenta burlar: ValueError!

juntando tudo

Classe completa

conta_completa.py
class ContaBancaria:
    def __init__(self, titular, saldo):
        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, valor):
        if valor > 0: self.saldo += valor

    def sacar(self, valor):
        if valor > self.saldo:
            print("Saldo insuficiente"); return
        self.saldo -= valor

    def __str__(self):
        return f"Conta de {self.titular} · Saldo: R$ {self.saldo:.2f}"

c = ContaBancaria("Ana", 1000)
c.depositar(500)
c.sacar(200)
print(c)  # Conta de Ana · Saldo: R$ 1300.00
Dica: Note que sacar e depositar usam self.saldo (a property). Isso garante que qualquer alteração passe pela validação no @setter!

exercício

Refatorando a classe Produto

Modifique a classe abaixo para garantir a integridade dos dados através do encapsulamento.

Parte 1 · proteger: Transforme preco e estoque em atributos internos (_). Adicione @property para lê-los.
Parte 2 · @setter: Adicione setters com validação: valor não pode ser negativo. Use os nomes limpos no __init__.
Parte 3 · métodos: Crie vender() e repor() que usam a property — e um __str__ para exibir os dados.
produto_original.py
class Produto:
    def __init__(self, nome, preco, estoque):
        self.nome    = nome
        self.preco   = preco
        self.estoque = estoque

# Erro: permite preço e estoque negativos!
p = Produto("Mouse", -50, -10)
Objetivo: O código deve lançar ValueError se alguém tentar criar um produto com preço ou estoque negativo.

exercício · solução

Produto com encapsulamento

produto_completo.py
class Produto:
    def __init__(self, nome, preco, estoque):
        self.nome    = nome
        self.preco   = preco    # usa o setter
        self.estoque = estoque

    @property
    def preco(self): return self._preco

    @preco.setter
    def preco(self, v):
        if v < 0: raise ValueError("Preço não pode ser negativo")
        self._preco = v

    @property
    def estoque(self): return self._estoque

    @estoque.setter
    def estoque(self, v):
        if v < 0: raise ValueError("Estoque não pode ser negativo")
        self._estoque = v

    def vender(self, qtd): self.estoque -= qtd
    def repor(self, qtd):   self.estoque += qtd

    def __str__(self):
        return f"{self.nome} · R$ {self.preco:.2f} · {self.estoque} un."

atenção

Erros comuns com @property

1

Recursão infinita

Usar self.saldo = valor dentro do setter em vez de self._saldo. Isso faz o setter chamar a si mesmo sem parar.

2

Setter sem property

Tentar criar o setter antes de definir o @property (getter). O Python exige que a base da property exista primeiro.

3

Nomes diferentes

Usar def saldo na property e def set_saldo no setter. Devem ter exatamente o mesmo nome para o Python vinculá-los.

4

Burlar a validação

Usar self._saldo nos métodos internos. Se você tem regras no setter, use sempre o nome limpo self.saldo para que elas sejam respeitadas.

fechamento

O que vimos hoje

  • Encapsulamento — esconder detalhes internos e controlar o acesso
  • _atributo — convenção "uso interno, não acesse diretamente"
  • __atributo — name mangling, proteção real contra acesso externo
  • @property — leitura de atributo como se fosse público
  • @nome.setter — escrita com validação automática

para casa

15 exercícios gradativos

Faça o máximo que conseguir e anote suas dúvidas para a próxima aula.

4

Fácil

Exercícios: 1 ao 4

5

Médio

Exercícios: 5 ao 9

4

Difícil

Exercícios: 10 ao 13

2

Avançado

Exercícios: 14 e 15

exercícios para casa · nível fácil

Nível Fácil

01

Temperatura protegida

Crie Temperatura com _celsius. Adicione @property para leitura e método exibir(self) que mostra o valor em Celsius e Fahrenheit.

02

Pessoa somente leitura

Crie Pessoa com _nome e _idade. Adicione @property para ambos — sem setter. Tente alterar o nome diretamente e observe o erro.

03

Produto somente leitura

Crie Produto com _preco e _estoque. Adicione @property para leitura. Adicione exibir(self) que imprime as informações.

04

Retângulo com validação

Crie Retangulo com _largura e _altura. Setters impedem valores menores ou iguais a zero. Métodos area() e perimetro().

exercícios para casa · nível médio

Nível Médio

05

Conta Bancária com @property

Implemente ContaBancaria completa do slide 10. Saldo com @property e setter que impede negativos. Métodos depositar, sacar e __str__.

06

Aluno com nota validada

Crie Aluno com _nome, _matricula e _notas. Método adicionar_nota(nota) só aceita valores entre 0 e 10. Adicione media() e situacao().

07

Estoque com mínimo

Crie Estoque com _quantidade e _minimo. O setter impede valores abaixo do mínimo e exibe aviso quando estiver próximo. Métodos entrar(qtd) e sair(qtd).

08

Velocímetro

Crie Velocimetro com _velocidade (inicia em 0) e _limite. Setter impede negativos e acima do limite. Métodos acelerar(delta) e frear(delta).

09

Senha protegida

Crie Usuario com _nome e _senha. Setter da senha exige mínimo de 8 caracteres. Método verificar_senha(tentativa) retorna True/False sem expor a senha.

exercícios para casa · nível difícil

Nível Difícil

10

Pessoa com CPF protegido

Crie Pessoa com _cpf. O setter valida o formato XXX.XXX.XXX-XX (use len() e count("-")). O __str__ exibe o CPF mascarado usando fatiamento: "***" + self._cpf[3:11] + "**".

11

Conta com histórico

Expanda o exercício 5. Adicione _historico (lista privada). Toda operação registra tipo e valor. extrato como @property retorna o histórico.

12

Produto com desconto calculado

Crie Produto com _preco_original e _desconto (0 a 100). @property preco_final calcula o preço com desconto. Setter de desconto impede valores fora de 0–100.

13

Termostato inteligente

Crie Termostato com _temperatura_atual e _temperatura_alvo. @property modo retorna 'aquecendo', 'resfriando' ou 'estável'. Setter de alvo limita entre 15 e 30 graus.

exercícios para casa · nível avançado

Nível Avançado

14

Fração encapsulada

Crie Fracao com _numerador e _denominador. Setter do denominador impede zero. @property valor retorna o resultado. __add__ e __eq__ para somar e comparar frações.

15

Banco com múltiplas contas

Crie Banco com _contas (lista privada). Métodos abrir_conta, fechar_conta, buscar_conta. @property total_em_depositos soma todos os saldos de todas as contas cadastradas.

próxima aula

Aula 5 · 15/04

Construtor __init__ e composição

Vamos explorar o construtor em detalhes, aprender a usar parâmetros opcionais e entender como objetos podem conter outros objetos.

parâmetros opcionais objetos dentro de objetos composição agregação