Módulo 11 - Orientação a objetos: herança

Encapsulamento

11 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Entender encapsulamento como esconder detalhes e expor o essencial.
  • Usar a convenção do underscore para marcar atributos internos.
  • Conhecer, em pincelada, o @property para controlar acesso.
  • Explicar por que esconder detalhes deixa o objeto mais seguro.

O que é encapsular

Pense no controle remoto da televisão. Você aperta um botão e o volume sobe; não precisa saber nada dos circuitos por dentro. O controle esconde a complexidade e oferece só botões claros. Encapsulamento é isso aplicado a objetos: a classe guarda os seus dados e a sua lógica, mas expõe para o resto do programa apenas uma interface simples, os métodos públicos. Quem usa a classe conversa por essa interface e não precisa, nem deve, mexer nas peças internas. Isso deixa o objeto mais fácil de usar e mais difícil de estragar por engano.

Por que esconder? Porque separar o que é público do que é interno dá liberdade para melhorar as tripas do objeto sem quebrar quem o usa. Se todo mundo mexe direto num atributo, você fica preso àquele formato para sempre. Se o acesso passa por métodos, você pode validar, calcular ou mudar a implementação por dentro, mantendo a mesma fachada. Encapsulamento é, no fundo, sobre fronteira: aqui é a parte estável que os outros usam, ali é o detalhe que eu me reservo o direito de mudar.

A convenção do underscore

Diferente de outras linguagens, o Python não tranca atributos com palavras como private. Ele usa uma convenção respeitada pela comunidade: um underscore no começo do nome, como self._saldo, sinaliza este atributo é interno, não mexa direto de fora. O Python não impede o acesso; é um acordo de cavalheiros. Quem vê o underscore entende que aquilo é detalhe da classe e que deve usar os métodos públicos. Essa clareza vale muito: o próprio nome documenta a intenção.

class ContaBancaria:
    def __init__(self, saldo_inicial):
        self._saldo = saldo_inicial   # interno, por convenção

    def depositar(self, valor):
        if valor <= 0:
            raise ValueError("Valor deve ser positivo.")
        self._saldo += valor

    def sacar(self, valor):
        if valor > self._saldo:
            raise ValueError("Saldo insuficiente.")
        self._saldo -= valor

    def ver_saldo(self):
        return self._saldo

c = ContaBancaria(100)
c.depositar(50)
c.sacar(30)
print(c.ver_saldo())  # 120

O _saldo é interno; depositar, sacar e ver_saldo são a interface pública com regras.

A conta guarda o saldo em _saldo, marcado como interno. Ninguém deveria escrever c._saldo = 1000000 direto; o acesso passa por depositar e sacar, que validam. Depositar recusa valores negativos; sacar recusa tirar mais do que há. Assim, o saldo nunca fica num estado inválido, porque toda alteração passa por uma porta com regras. Esse é o valor prático do encapsulamento: os métodos públicos protegem a integridade dos dados internos, em vez de deixar qualquer um mexer sem critério.

Propriedades com @property, em pincelada

Às vezes você quer expor um valor para leitura, mas ainda controlar como ele é obtido ou impedir que seja escrito de qualquer jeito. O @property resolve isso: ele faz um método parecer um atributo. Quem usa escreve c.saldo, sem parênteses, como se fosse um dado simples, mas por trás roda um método seu, que pode calcular ou validar. É uma pincelada aqui, só para você reconhecer quando vir; o aprofundamento fica para depois. O que importa agora é a ideia: interface limpa por fora, controle por dentro.

class Produto:
    def __init__(self, preco):
        self._preco = preco

    @property
    def preco(self):
        return self._preco       # leitura controlada

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

p = Produto(10)
print(p.preco)   # 10  (parece atributo, mas roda um método)
p.preco = 15     # passa pela validação do setter
print(p.preco)   # 15

Com @property, p.preco parece um atributo mas passa por métodos de leitura e escrita.

Repare que quem usa Produto escreve p.preco e p.preco = 15, uma sintaxe simples de atributo. Só que, por baixo, a leitura passa pelo método marcado com @property e a escrita passa pelo setter, que recusa preço negativo. O melhor: se amanhã você começar com _preco público e depois precisar validar, dá para trocar por uma propriedade sem mudar uma linha de quem usa a classe. Essa é a promessa do encapsulamento: a fachada fica igual, e você fica livre para melhorar o interior.

Teste rápido

O que a convenção de um underscore no início do nome, como self._saldo, comunica?

Perguntas frequentes

O Python tem atributos realmente privados?
Não como Java ou C#. Ele usa a convenção do underscore para dizer isto é interno. Um duplo underscore ativa um embaralhamento de nome que dificulta o acesso acidental, mas ainda é possível chegar lá. A filosofia é confiar em quem programa e comunicar intenção, não trancar tudo à força.
Por que não deixar todos os atributos públicos e acabou?
Porque aí qualquer parte do programa pode pôr o objeto num estado inválido, como um saldo negativo. Passando o acesso por métodos, você valida cada mudança e mantém a integridade dos dados. Também ganha liberdade para mudar o interior depois sem quebrar quem usa a classe.
Quando devo usar @property em vez de um método normal?
Use @property quando quiser que algo pareça um atributo simples, como p.preco, mas ainda controlar leitura ou escrita por baixo. Se a operação é claramente uma ação, como sacar dinheiro, um método comum com parênteses comunica melhor. Property brilha para valores lidos ou escritos como dados.
Qual a diferença entre um e dois underscores no início?
Um underscore, como _saldo, é só convenção: interno, não mexa. Dois underscores, como __saldo, fazem o Python embaralhar o nome internamente, o que evita acesso acidental e colisões em herança, mas não é segurança de verdade. No dia a dia, um underscore resolve a maioria dos casos.
Encapsulamento tem a ver com herança?
São pilares diferentes da orientação a objetos, mas se completam. Herança organiza o que classes compartilham; encapsulamento define o que cada uma esconde e expõe. Numa hierarquia bem feita, a base encapsula os seus detalhes e as filhas conversam com ela pela interface pública, sem depender do interior.
Preciso escrever getters e setters para tudo?
Não, e em Python isso seria antiquado. Comece com atributos simples e só adicione @property quando precisar validar ou calcular no acesso. A vantagem do Python é que dá para começar simples e migrar para propriedade depois, sem mudar quem usa a classe. Não crie cerimônia antes de precisar.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.