O encapsulamento é um dos pilares da programação orientada a objetos, mas em Python ele é implementado de forma peculiar. Ao contrário de linguagens como Java ou C++, que possuem palavras-chave como private e protected, Python adota uma abordagem baseada em convenções e em um mecanismo sutil chamado name mangling. Nesta aula, vamos explorar como proteger dados e expor interfaces limpas, respeitando a filosofia de que "todos somos adultos".

Entender essas técnicas é essencial para escrever código Python idiomático, manter a integridade dos objetos e criar APIs que comuniquem claramente o que é público e o que é interno. Vamos começar com as convenções de nomenclatura e depois mergulhar em propriedades e boas práticas.

Convenção _ e __

Em Python, não existem modificadores de acesso rígidos. Em vez disso, usamos convenções para indicar a intenção de uso de um atributo ou método. Um único underscore (_) no início de um nome é uma convenção para indicar que o membro é "interno" ou "protegido". Ou seja, ele não faz parte da API pública e deve ser usado com cuidado, mas ainda pode ser acessado diretamente se necessário.

Dois underscores (__) no início de um nome (mas não no final) ativam o name mangling, que altera o nome do atributo internamente para evitar colisões em herança. Isso é uma forma mais forte de "privacidade", mas ainda assim não é uma barreira de segurança; é uma convenção que indica que o atributo não deve ser acessado de fora da classe.

Veja um exemplo prático:

class Pessoa:
    def __init__(self, nome, idade):
        self._nome = nome       # convenção de protegido
        self.__idade = idade    # name mangling

    def apresentar(self):
        print(f"Olá, eu sou {self._nome} e tenho {self.__idade} anos.")

p = Pessoa("Alice", 30)
print(p._nome)          # Acessível, mas não recomendado
# print(p.__idade)      # Erro! Atributo não encontrado
print(p._Pessoa__idade) # Acesso via name mangling (possível, mas evite)

Observe que _nome pode ser acessado diretamente, enquanto __idade é "mangled" para _Pessoa__idade. Isso evita que subclasses sobrescrevam acidentalmente um atributo com o mesmo nome.

Name mangling

O name mangling é um mecanismo automático que ocorre quando você usa dois underscores no início de um nome de atributo ou método dentro de uma classe. O interpretador Python reescreve o nome para _NomeDaClasse__atributo. Isso é feito para reduzir o risco de conflitos em herança múltipla e para tornar o atributo efetivamente "privado".

Na prática, isso significa que o atributo não é acessível diretamente pelo nome original fora da classe. No entanto, ele ainda pode ser acessado se você souber o nome mangled, o que é desencorajado. O principal benefício é que, se uma subclasse definir um atributo com o mesmo nome, ele não sobrescreverá o da classe base.

Vamos ver um exemplo com herança:

class Animal:
    def __init__(self):
        self.__tipo = "animal"

    def get_tipo(self):
        return self.__tipo

class Cachorro(Animal):
    def __init__(self):
        super().__init__()
        self.__tipo = "cachorro"  # não sobrescreve o __tipo da classe Animal

c = Cachorro()
print(c.get_tipo())  # "animal" - o __tipo da classe Animal permanece intacto
print(c._Cachorro__tipo)  # "cachorro" - o novo atributo

Note que __tipo na classe Cachorro é um atributo diferente do __tipo da classe Animal devido ao mangling. Isso evita confusões e é especialmente útil em frameworks que usam muitos mixins.

Propriedades

Propriedades são uma forma elegante de encapsular atributos, permitindo que você controle o acesso e a atribuição de valores. Em Python, usamos o decorador @property para definir um método que será acessado como um atributo, e @nome.setter para controlar a atribuição. Isso permite adicionar lógica de validação sem alterar a interface pública.

Vamos criar uma classe Temperatura que armazena a temperatura em Celsius, mas permite obter e definir em Fahrenheit:

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

    @fahrenheit.setter
    def fahrenheit(self, valor):
        self.celsius = (valor - 32) * 5/9

t = Temperatura(25)
print(t.celsius)     # 25
t.celsius = 30
print(t.fahrenheit)  # 86.0
t.fahrenheit = 100
print(t.celsius)     # 37.777...

As propriedades são uma forma de encapsulamento que permite que você mude a implementação interna sem afetar o código que usa a classe. Elas também são amplamente usadas em bibliotecas para criar APIs limpas e seguras.

Filosofia ("we are all adults")

A filosofia de Python, resumida na expressão "we are all adults", reflete a ideia de que a linguagem confia no programador e não impõe restrições rígidas. Não há necessidade de private ou protected porque o Python acredita que cada desenvolvedor é responsável por usar os recursos de forma adequada.

Isso significa que as convenções de underscores e as propriedades são ferramentas para comunicar intenção, não para impedir acesso. O acesso a atributos "privados" é sempre possível, mas é desencorajado. Essa filosofia incentiva a transparência e a simplicidade, ao mesmo tempo que oferece mecanismos para proteger dados quando necessário.

Na prática, isso se traduz em:

  • Usar _ para indicar que um membro é interno, mas não se preocupar em escondê-lo completamente.
  • Usar __ apenas quando houver risco de colisão em herança.
  • Usar propriedades para controle fino de acesso sem sacrificar a sintaxe de atributos.
  • Respeitar as convenções e confiar nos colegas de equipe para não abusar do acesso direto.

Essa abordagem torna o código mais limpo e menos burocrático, permitindo que você se concentre na lógica de negócio.

Boas práticas e observações finais

Ao trabalhar com encapsulamento em Python, lembre-se de:

  • Prefira propriedades a métodos getters/setters tradicionais, a menos que você precise de comportamento específico.
  • Use _ para atributos internos e __ apenas quando necessário para evitar conflitos de herança.
  • Documente claramente o que é parte da API pública e o que é interno, usando docstrings e comentários.
  • Evite usar o name mangling como mecanismo de segurança; ele é apenas uma convenção.
  • Teste as propriedades para garantir que as validações funcionem como esperado.

O encapsulamento em Python é uma habilidade essencial para criar código robusto e sustentável. Com essas técnicas, você pode proteger seus dados e oferecer interfaces claras.

Referências

Exercícios

  1. Explique a diferença entre _atributo e __atributo em Python. Dê um exemplo de quando usar cada um.

    ✓ Resposta: _atributo é uma convenção que indica que o atributo é interno (protegido), mas ainda pode ser acessado diretamente. __atributo ativa o name mangling, tornando o nome efetivamente privado (mas ainda acessível via _Classe__atributo). Use _ para indicar "não use fora da classe" e __ para evitar colisões em herança.
  2. Escreva uma classe ContaBancaria com um atributo __saldo (privado) e métodos depositar e sacar que validem as operações. Use name mangling para proteger o saldo.

    ✓ Resposta:
    class ContaBancaria:
        def __init__(self, saldo_inicial):
            self.__saldo = saldo_inicial
    
        def depositar(self, valor):
            if valor <= 0:
                raise ValueError("Valor de depósito deve ser positivo")
            self.__saldo += valor
    
        def sacar(self, valor):
            if valor <= 0 or valor > self.__saldo:
                raise ValueError("Saque inválido")
            self.__saldo -= valor
    
        def ver_saldo(self):
            return self.__saldo
  3. Crie uma classe Retangulo com propriedades para largura e altura, garantindo que sejam positivos. Adicione uma propriedade area que calcule a área automaticamente.

    ✓ Resposta:
    class Retangulo:
        def __init__(self, largura, altura):
            self.largura = largura
            self.altura = altura
    
        @property
        def largura(self):
            return self._largura
    
        @largura.setter
        def largura(self, valor):
            if valor <= 0:
                raise ValueError("Largura deve ser positiva")
            self._largura = valor
    
        @property
        def altura(self):
            return self._altura
    
        @altura.setter
        def altura(self, valor):
            if valor <= 0:
                raise ValueError("Altura deve ser positiva")
            self._altura = valor
    
        @property
        def area(self):
            return self._largura * self._altura
  4. Explique por que Python não possui modificadores de acesso como private e protected. Como a filosofia "we are all adults" influencia o design da linguagem?

    ✓ Resposta: Python acredita que os desenvolvedores são responsáveis e não precisam de restrições impostas pela linguagem. Em vez disso, convenções e mecanismos como name mangling e propriedades oferecem controle sem impedir o acesso. Isso promove código mais flexível e menos burocrático, alinhado à filosofia "we are all adults".
  5. Implemente uma classe Pessoa com um atributo __idade (privado) e uma propriedade idade que valide se o valor está entre 0 e 150. Use o decorador @property e @idade.setter.

    ✓ Resposta:
    class Pessoa:
        def __init__(self, idade):
            self.idade = idade
    
        @property
        def idade(self):
            return self.__idade
    
        @idade.setter
        def idade(self, valor):
            if not (0 <= valor <= 150):
                raise ValueError("Idade deve estar entre 0 e 150")
            self.__idade = valor