Encapsulamento é o princípio de agrupar dados e comportamentos em um objeto e controlar o acesso a esses dados. É um dos quatro pilares da POO.
| Sintaxe | Nível | Efeito |
|---|---|---|
atributo | Público | Acesso livre |
_atributo | Protegido | Convenção — evite acesso externo |
__atributo | Privado | Name mangling — dificulta acesso externo |
Em Python, encapsulamento é mais sobre design e comunicação do que sobre segurança técnica absoluta. O objetivo é deixar claro o que é interno e o que é interface pública.
Uma convenção da comunidade Python. Significa: "este atributo é de uso interno — se você acessar diretamente, por sua conta e risco". Python não impede nada tecnicamente.
class Pessoa:
def __init__(self, nome, cpf):
self.nome = nome
self._cpf = cpf # interno
p = Pessoa("Ana", "123.456.789-00")
print(p._cpf) # funciona, mas é má prática Ativa o name mangling: Python renomeia internamente para _NomeDaClasse__atributo. Dificulta acesso externo e evita conflitos em herança.
class Conta:
def __init__(self, saldo):
self.__saldo = saldo
c = Conta(1000)
print(c.__saldo) # AttributeError!
print(c._Conta__saldo) # 1000 (existe, mas feio) _ para a maioria dos casos — comunica intenção sem complicar__ quando precisar proteger contra sobrescrita em subclasses__ se não houver herança envolvida — é mais complexo sem benefício claroQuando você escreve self.__saldo, Python renomeia para _NomeDaClasse__saldo. O objetivo principal é evitar conflito acidental em herança:
class Animal:
def __init__(self):
self.__tipo = "animal" # vira _Animal__tipo
class Cachorro(Animal):
def __init__(self):
super().__init__()
self.__tipo = "chorro" # vira _Cachorro__tipo
# Os dois coexistem sem conflito! class Conta:
def __init__(self, saldo):
self.__saldo = saldo
c = Conta(1000)
print(c._Conta__saldo) # 1000 Na prática, se você está acessando _Classe__atributo de fora, há um problema de design. Python prioriza a liberdade; use essa ferramenta para evitar conflitos, não para criar barreiras artificiais. Exponha via @property se precisar de acesso legítimo.
São métodos criados para controlar o acesso aos atributos de um objeto:
get_saldo().set_saldo(valor).Se você acessa self._saldo diretamente de fora, você pula todas as regras. O Setter funciona como um segurança na porta: ele impede que dados absurdos (como saldo negativo ou texto onde deveria ser número) entrem no seu objeto.
Como você viu no slide, usar conta.set_saldo(1000) funciona, mas deixa o código "barulhento" e cansativo de ler. No Python, preferimos que tudo pareça um atributo comum, e é aí que entra o @property.
O @property funciona como o nosso Getter. Ele é um decorador que transforma um método em um atributo de leitura, permitindo que quem usa a classe acesse o valor sem precisar chamar parênteses.
class Circulo:
def __init__(self, raio):
self._raio = raio
@property
def area(self):
return 3.14159 * self._raio ** 2
c = Circulo(5)
print(c.area) # 78.53... — calculado na hora
c.area = 10 # AttributeError — sem setter! class Produto:
def __init__(self, preco):
self.preco = preco # chama o setter!
@property
def preco(self):
return self._preco # note o _preco interno
@preco.setter
def preco(self, valor):
if valor < 0:
raise ValueError("Preço não pode ser negativo")
self._preco = valor Dentro do setter, use sempre self._preco. Se usar self.preco, o setter chama a si mesmo em recursão infinita.
@preco.setter
def preco(self, valor):
self.preco = valor # chama o setter de novo! @preco.setter
def preco(self, valor):
self._preco = valor # armazena no atributo interno raise ValueError("mensagem") lança uma exceção quando o valor é inválido. Veremos exceções em detalhes na aula 11 — por ora, pense como uma forma de sinalizar erro com mensagem clara.
Ocorre quando você esquece o underscore dentro do setter. O comando self.atributo = valor chama o próprio setter em um loop eterno.
Se a property se chama saldo, o setter PRECISA se chamar @saldo.setter. Se você criar set_saldo, o Python não fará o vínculo automático.
A property (getter) deve ser declarada sempre antes do setter no código.
Programação Orientada a Objetos com Python
Tecnologia em Análise e Desenvolvimento de Sistemas
motivação
Quando os atributos ficam abertos, qualquer código pode alterá-los sem validação.
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 conceito fundamental
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.
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
Um underscore é uma convenção — um aviso para outros desenvolvedores. Python não bloqueia, mas sinaliza que o atributo é interno.
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 _ é um acordo entre desenvolvedores, não uma barreira técnica.nível 2 de proteção · herança
O objetivo principal é evitar conflito acidental em herança, impedindo que subclasses sobrescrevam atributos vitais.
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!) __ dificulta o acesso para proteger a arquitetura da classe.comparação
Na prática profissional, _ é suficiente na maioria dos casos. Use __ quando precisar garantir integridade em heranças.
self._saldo = saldo
self.__saldo = saldo
controlando o acesso
Antes da forma elegante do Python, entenda o conceito: métodos que leem (get) e alteram (set) atributos com validação.
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 a forma pythônica
@property transforma um método em um atributo calculado. Você acessa como um dado comum, mas o valor é processado na hora.
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 @property sem um setter cria um atributo de somente leitura.controlando a escrita
O setter permite alterar o atributo com validação automática. A sintaxe usa o nome da property mais .setter.
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
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 self.saldo (a property). Isso garante que qualquer alteração passe pela validação no @setter!exercício
Modifique a classe abaixo para garantir a integridade dos dados através do encapsulamento.
preco e estoque em atributos internos (_). Adicione @property para lê-los.__init__.vender() e repor() que usam a property — e um __str__ para exibir os dados.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) ValueError se alguém tentar criar um produto com preço ou estoque negativo.exercício · solução
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
Usar self.saldo = valor dentro do setter em vez de self._saldo. Isso faz o setter chamar a si mesmo sem parar.
Tentar criar o setter antes de definir o @property (getter). O Python exige que a base da property exista primeiro.
Usar def saldo na property e def set_saldo no setter. Devem ter exatamente o mesmo nome para o Python vinculá-los.
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
_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
Faça o máximo que conseguir e anote suas dúvidas para a próxima aula.
Exercícios: 1 ao 4
Exercícios: 5 ao 9
Exercícios: 10 ao 13
Exercícios: 14 e 15
exercícios para casa · nível fácil
Crie Temperatura com _celsius. Adicione @property para leitura e método exibir(self) que mostra o valor em Celsius e Fahrenheit.
Crie Pessoa com _nome e _idade. Adicione @property para ambos — sem setter. Tente alterar o nome diretamente e observe o erro.
Crie Produto com _preco e _estoque. Adicione @property para leitura. Adicione exibir(self) que imprime as informações.
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
Implemente ContaBancaria completa do slide 10. Saldo com @property e setter que impede negativos. Métodos depositar, sacar e __str__.
Crie Aluno com _nome, _matricula e _notas. Método adicionar_nota(nota) só aceita valores entre 0 e 10. Adicione media() e situacao().
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).
Crie Velocimetro com _velocidade (inicia em 0) e _limite. Setter impede negativos e acima do limite. Métodos acelerar(delta) e frear(delta).
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
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] + "**".
Expanda o exercício 5. Adicione _historico (lista privada). Toda operação registra tipo e valor. extrato como @property retorna o histórico.
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.
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
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.
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.
Vamos explorar o construtor em detalhes, aprender a usar parâmetros opcionais e entender como objetos podem conter outros objetos.