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):
atributooumetodo() - Protegido (convenção):
_atributoou_metodo() - Privado (name mangling):
__atributoou__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
- PEP 8 – Style Guide for Python Code — Seção sobre nomenclatura de atributos privados e protegidos na guia oficial de estilo Python
- Python Official Documentation: Classes — Documentação oficial sobre variáveis privadas e name mangling
- Real Python: Python's Property Decorator — Tutorial completo sobre @property para encapsulamento elegante
- GeeksforGeeks: Encapsulation in Python — Explicação detalhada com exemplos de público, protegido e privado
- Python Official Documentation: Descriptor Protocol — Aprofundamento em como @property funciona internamente via protocolo de descritores