Encapsulamento: convenções de privacidade em Python

1. Introdução ao encapsulamento em Python

Encapsulamento é um dos pilares da programação orientada a objetos. Ele consiste em ocultar os detalhes internos de uma classe, expondo apenas uma interface controlada para interação com o mundo externo. Em linguagens como Java e C++, isso é imposto rigidamente por meio de palavras-chave como private, protected e public.

Python adota uma filosofia diferente, frequentemente resumida pela expressão "somos todos adultos consentindo". Aqui, não há modificadores de acesso verdadeiros — tudo é acessível em tempo de execução. Em vez de barreiras intransponíveis, Python oferece convenções de nomenclatura que sinalizam a intenção de privacidade. Essas convenções são:

  • Público (padrão): atributo ou metodo()
  • Protegido (convenção): _atributo ou _metodo()
  • Privado (name mangling): __atributo ou __metodo()

Vamos explorar cada um desses níveis e entender quando e por que utilizá-los.

2. Atributos e métodos públicos (padrão)

Em Python, tudo é público por padrão. Isso significa que qualquer código pode acessar e modificar atributos ou chamar métodos de uma instância diretamente.

class Pessoa:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def apresentar(self):
        return f"Olá, sou {self.nome} e tenho {self.idade} anos."

p = Pessoa("Alice", 30)
print(p.nome)          # Alice
print(p.apresentar())  # Olá, sou Alice e tenho 30 anos.
p.idade = 31           # Modificação direta permitida

Atributos e métodos públicos são adequados quando você deseja que a API da classe seja estável, simples e sem restrições. Eles formam o contrato público da sua classe — o que os usuários podem esperar usar livremente.

3. Convenção de atributos "protegidos" com underscore simples (_)

O underscore simples é uma convenção, não uma imposição. Ele sinaliza: "este atributo/método é interno à implementação e não deve ser acessado diretamente por código externo".

class ContaBancaria:
    def __init__(self, titular, saldo_inicial):
        self.titular = titular
        self._saldo = saldo_inicial  # Convenção: protegido

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

    def _calcular_juros(self):  # Método interno
        return self._saldo * 0.01

conta = ContaBancaria("João", 1000)
print(conta._saldo)  # Funciona, mas é desaconselhado
conta._calcular_juros()  # Também funciona

Na prática, o underscore simples é amplamente usado para:
- Atributos que são parte da implementação interna
- Métodos auxiliares que não fazem parte da API pública
- Prevenção de conflitos de nomes em bibliotecas

Subclasses podem acessar _saldo livremente — a convenção se aplica principalmente ao código externo à hierarquia de classes.

4. Name mangling com underscore duplo (__) para "privacidade"

Quando você usa dois underscores no início de um nome (mas não no final), o Python aplica name mangling: o nome é transformado internamente para _Classe__atributo. Isso dificulta (mas não impede) o acesso externo.

class ContaSegura:
    def __init__(self, senha):
        self.__senha = senha  # Será transformado

    def verificar_senha(self, tentativa):
        return self.__senha == tentativa

conta = ContaSegura("1234")
# print(conta.__senha)  # AttributeError!
print(conta._ContaSegura__senha)  # Funciona: "1234"

O name mangling serve principalmente para evitar conflitos em herança. Se uma subclasse definir um atributo com o mesmo nome, ele não sobrescreverá acidentalmente o atributo da classe pai:

class ContaPremium(ContaSegura):
    def __init__(self, senha, bonus):
        super().__init__(senha)
        self.__senha = bonus  # Não conflita com ContaSegura.__senha

premium = ContaPremium("1234", 500)
print(premium._ContaSegura__senha)  # "1234"
print(premium._ContaPremium__senha)  # 500 (atributo diferente)

Use __ quando você realmente precisa evitar que subclasses sobrescrevam acidentalmente um atributo interno. Evite usar em excesso, pois dificulta testes e depuração.

5. Métodos mágicos e dunder methods (__init__, __str__, etc.)

Métodos dunder (double underscore) como __init__, __str__ e __repr__ são reservados para o interpretador Python. Eles têm um propósito especial e nunca devem ser criados por você com nomes como __meu_metodo__.

class Ponto:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Ponto({self.x}, {self.y})"

    def __str__(self):
        return f"({self.x}, {self.y})"

p = Ponto(3, 4)
print(repr(p))  # Ponto(3, 4)
print(p)        # (3, 4)

A diferença fundamental entre dunders e name mangling é o propósito:
- Dunders: personalizam o comportamento de operações da linguagem (soma, string, iteração)
- Name mangling (__): protege atributos contra sobrescrita acidental em subclasses

Nunca invente seus próprios dunders — a comunidade Python reserva esse espaço para futuras extensões da linguagem.

6. Propriedades (@property) como controle de acesso

Propriedades permitem implementar getters e setters sem quebrar a API pública. Você começa com um atributo público simples e, se precisar adicionar validação ou lógica, converte-o em propriedade — o código cliente continua funcionando sem alterações.

class Temperatura:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, valor):
        if valor < -273.15:
            raise ValueError("Temperatura abaixo do zero absoluto!")
        self._celsius = valor

    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32

temp = Temperatura(25)
print(temp.celsius)       # 25 (getter)
temp.celsius = 30         # setter com validação
# temp.celsius = -300     # ValueError!
print(temp.fahrenheit)    # 86.0 (propriedade readonly)

Benefícios das propriedades:
- Encapsulamento sem quebrar a API (código cliente continua usando objeto.atributo)
- Validação no setter
- Propriedades readonly (apenas getter) para imutabilidade controlada
- Cálculos sob demanda (como fahrenheit)

7. Boas práticas e padrões comuns

Aqui está um guia prático de decisão:

Convenção Uso recomendado Exemplo típico
Público API estável, sem restrições self.nome, def calcular_imposto()
_ protegido Implementação interna, subclasses podem acessar self._saldo, def _validar_dados()
__ privado Evitar conflitos em herança self.__id_unico, def __processar_interno()

Recomendações adicionais:
- Use _ para métodos internos (_calcular_juros, _validar_dados)
- Evite __ em excesso — dificulta testes unitários e depuração
- Documente a intenção de privacidade com docstrings
- Prefira @property a getters/setters estilo Java

class Pedido:
    def __init__(self, itens):
        self._itens = itens
        self._processado = False

    def _calcular_total(self):
        """Método interno: calcula soma dos itens."""
        return sum(item.preco for item in self._itens)

    @property
    def total(self):
        """Propriedade pública que expõe o total calculado."""
        return self._calcular_total()

    def finalizar(self):
        """Método público da API."""
        self._processado = True
        # lógica de finalização...

8. Conclusão e comparação com temas vizinhos

Encapsulamento em Python é baseado em confiança e convenção, não em barreiras técnicas. As convenções _ e __ são ferramentas de comunicação entre desenvolvedores, não mecanismos de segurança.

Na herança:
- _atributo é acessível por subclasses (uso interno esperado)
- __atributo com name mangling evita conflitos, mas ainda é acessível via _Classe__atributo

Métodos de classe (@classmethod) e estáticos (@staticmethod) seguem as mesmas regras de acesso — use _ ou __ conforme necessário.

Para aprofundar, explore o Method Resolution Order (MRO) em herança múltipla, que pode tornar o comportamento de atributos com __ ainda mais relevante em hierarquias complexas.

O encapsulamento em Python nos lembra que código é, acima de tudo, comunicação entre pessoas. As convenções existem para tornar essa comunicação mais clara e evitar acidentes — não para criar fortalezas intransponíveis.

Referências