Encapsulamento é um dos pilares da programação orientada a objetos, que consiste em esconder os detalhes internos de uma classe e expor apenas uma interface controlada. Em Python, o encapsulamento não é imposto rigidamente como em linguagens como Java ou C++; em vez disso, utiliza-se convenções e mecanismos sutis para indicar a intenção de proteger atributos e métodos.

Nesta aula, exploraremos as convenções de nomenclatura com underscore simples (_) e duplo (__), o mecanismo de name mangling, as propriedades (properties) e a filosofia Python de "we are all adults" (todos somos adultos), que enfatiza a responsabilidade do programador em vez de restrições rígidas.

Convenção _ e __

Em Python, não existem modificadores de acesso como private ou protected. Em vez disso, usa-se convenções de nomenclatura para comunicar a intenção:

  • Underscore simples no início (_atributo): Indica que o atributo ou método é "protegido" ou interno à classe. É uma convenção, não uma imposição. Programadores são incentivados a não acessar diretamente esses membros fora da classe, mas é possível fazê-lo.
  • Underscore duplo no início (__atributo): Indica que o atributo ou método é "privado" e deve ser usado apenas dentro da classe. O Python aplica um mecanismo chamado name mangling para dificultar o acesso acidental.

Exemplo de código:

class Pessoa:
    def __init__(self, nome, idade):
        self._nome = nome      # protegido
        self.__idade = idade   # privado

    def exibir(self):
        print(f"Nome: {self._nome}, Idade: {self.__idade}")

p = Pessoa("Alice", 30)
print(p._nome)      # Acesso permitido (mas não recomendado)
# print(p.__idade)  # AttributeError: 'Pessoa' object has no attribute '__idade'
print(p._Pessoa__idade)  # Acesso via name mangling (possível, mas evite)

Note que o acesso a __idade diretamente gera um erro, mas o Python ainda permite acesso via _Classe__atributo. Isso mostra que o encapsulamento é mais uma convenção do que uma barreira.

Name Mangling

Name mangling é o processo pelo qual o Python altera o nome de atributos que começam com duplo underscore (e não terminam com duplo underscore). O interpretador adiciona um underscore seguido do nome da classe na frente do atributo. Por exemplo, __idade em uma classe Pessoa se torna _Pessoa__idade.

Isso é feito para evitar conflitos de nomes em subclasses, não para garantir privacidade. O mecanismo é útil quando você quer que um atributo não seja sobrescrito acidentalmente por uma subclasse.

Exemplo:

class Animal:
    def __init__(self):
        self.__especie = "Animal"

    def mostrar(self):
        print(self.__especie)

class Cachorro(Animal):
    def __init__(self):
        super().__init__()
        self.__especie = "Cachorro"  # Isso não sobrescreve o atributo da classe pai

    def mostrar(self):
        print(self.__especie)

a = Animal()
a.mostrar()  # Animal
c = Cachorro()
c.mostrar()  # Cachorro - mas o atributo __especie de Animal ainda existe

Name mangling é uma ferramenta para evitar acidentes, mas não deve ser usada como mecanismo de segurança.

Propriedades

Propriedades permitem controlar o acesso a atributos de forma elegante, usando métodos getter, setter e deleter. Com @property, você pode definir um método que será acessado como um atributo, mas com lógica adicional.

Exemplo:

class ContaBancaria:
    def __init__(self, saldo):
        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

    @saldo.deleter
    def saldo(self):
        print("Deletando saldo...")
        del self._saldo

conta = ContaBancaria(1000)
print(conta.saldo)   # 1000
conta.saldo = 1500   # usa o setter
# conta.saldo = -500 # raise ValueError
del conta.saldo       # usa o deleter

Propriedades são uma forma pythonica de encapsular atributos, permitindo validação e lógica sem expor métodos getter/setter verbosos.

Filosofia ("we are all adults")

A filosofia Python de "we are all adults" (somos todos adultos) reflete a ideia de que a linguagem confia no programador e não impõe restrições desnecessárias. Em vez de forçar o encapsulamento com palavras-chave como private, Python oferece convenções e mecanismos sutis, esperando que os desenvolvedores usem bom senso.

Isso significa que você pode acessar atributos "privados" se realmente precisar, mas deve evitar fazê-lo. A documentação e as convenções existem para guiar, não para prender. A responsabilidade é do programador em respeitar os contratos definidos pelas classes.

Na prática, use _ para indicar uso interno, __ para evitar conflitos em subclasses, e @property para controle refinado. Lembre-se: o objetivo é tornar o código mais legível e manutenível, não criar barreiras intransponíveis.

Boas Práticas

  • Prefira usar _ para atributos que não fazem parte da API pública.
  • Use __ apenas quando precisar evitar sobrescrita em subclasses.
  • Utilize propriedades para encapsular lógica de acesso a atributos.
  • Documente a interface pública da classe claramente.
  • Confie na convenção e não abuse de name mangling para esconder dados.

Referências

Exercícios

  1. Crie uma classe Funcionario com atributos privados __nome e __salario. Forneça métodos getter e setter usando propriedades, onde o setter valida que o salário é positivo.

    ✓ Resposta:
    class Funcionario:
        def __init__(self, nome, salario):
            self.__nome = nome
            self.__salario = salario
    
        @property
        def nome(self):
            return self.__nome
    
        @nome.setter
        def nome(self, valor):
            self.__nome = valor
    
        @property
        def salario(self):
            return self.__salario
    
        @salario.setter
        def salario(self, valor):
            if valor < 0:
                raise ValueError("Salário não pode ser negativo")
            self.__salario = valor
    
  2. Explique o que é name mangling e em que situação ele é útil.

    ✓ Resposta:

    Name mangling é o processo pelo qual o Python renomeia atributos que começam com duplo underscore (sem duplo underscore no final) adicionando um underscore e o nome da classe na frente. Por exemplo, __atributo em uma classe MinhaClasse se torna _MinhaClasse__atributo. Isso é útil para evitar conflitos de nomes em subclasses, garantindo que um atributo da classe base não seja sobrescrito acidentalmente por uma subclasse.

  3. O que significa a filosofia "we are all adults" em Python? Dê um exemplo de como ela se aplica ao encapsulamento.

    ✓ Resposta:

    A filosofia "we are all adults" significa que Python confia no programador para usar os recursos corretamente, sem impor restrições rígidas. No encapsulamento, isso se manifesta ao usar convenções como _ e __ em vez de palavras-chave private ou protected. O programador é responsável por respeitar a interface pública e não acessar atributos privados desnecessariamente.

  4. Considere a classe abaixo. Qual a diferença entre os atributos _a e __b? O que acontece se uma subclasse definir um atributo __b?

    class Base:
        def __init__(self):
            self._a = 1
            self.__b = 2
    

    ✓ Resposta:

    _a é uma convenção para indicar que o atributo é protegido, mas pode ser acessado diretamente. __b sofre name mangling, tornando-se _Base__b. Se uma subclasse definir __b, ela criará um novo atributo _Subclasse__b, sem sobrescrever o da classe base. Isso evita conflitos.

  5. Escreva uma classe Termometro que armazena a temperatura em Celsius internamente (_celsius), mas expõe uma propriedade temperatura que permite obter e definir o valor em Fahrenheit. O setter deve converter Fahrenheit para Celsius.

    ✓ Resposta:
    class Termometro:
        def __init__(self, celsius=0):
            self._celsius = celsius
    
        @property
        def temperatura(self):
            return self._celsius * 9/5 + 32
    
        @temperatura.setter
        def temperatura(self, fahrenheit):
            self._celsius = (fahrenheit - 32) * 5/9