---
title: "Encapsulamento em Python: O que É e Como Funciona | Python Brasil"
url: "https://python.dev.br/glossario/encapsulamento/"
markdown_url: "https://python.dev.br/glossario/encapsulamento.MD"
description: "Aprenda encapsulamento em Python: name mangling, property avançado, descritores, __getattr__, slots e a filosofia Python vs Java. Exemplos completos."
date: "2025-11-08"
author: ""
---

# Encapsulamento em Python: O que É e Como Funciona | Python Brasil

Aprenda encapsulamento em Python: name mangling, property avançado, descritores, __getattr__, slots e a filosofia Python vs Java. Exemplos completos.


## O que é Encapsulamento?

**Encapsulamento** é o princípio da POO que consiste em **esconder os detalhes internos** de uma classe e expor apenas o que é necessário. Isso protege os dados de modificações indevidas e permite alterar a implementação interna sem afetar quem usa a classe.

Em Python, o encapsulamento é feito por **convenção**, não por restrição obrigatória da linguagem. A filosofia Python é "somos todos adultos responsáveis" — os mecanismos de proteção servem como sinalização, não como barreiras intransponíveis.

## Convenções de acesso

Python usa prefixos de underscore para indicar o nível de acesso:

```python
class ContaBancaria:
    def __init__(self, titular, saldo):
        self.titular = titular        # Público: acesso livre
        self._agencia = "0001"        # Protegido: convenção, não restricao
        self.__saldo = saldo          # Privado: name mangling aplicado

    @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

conta = ContaBancaria("Maria", 1000)
print(conta.titular)   # Maria — acesso público
print(conta._agencia)  # 0001 — funciona, mas sinaliza "uso interno"
print(conta.saldo)     # 1000 — via property
```

## Name mangling em detalhe

Quando você usa `__atributo` (dois underscores antes, sem underscores depois), o Python aplica **name mangling**: renomeia o atributo internamente para `_NomeClasse__atributo`. Isso serve para evitar colisões de nomes em herança, não para criar acesso impossível.

```python
class Base:
    def __init__(self):
        self.__secreto = "valor da base"

    def revelar(self):
        return self.__secreto  # Funciona: acessa _Base__secreto

class Filha(Base):
    def __init__(self):
        super().__init__()
        self.__secreto = "valor da filha"  # _Filha__secreto — diferente!

    def revelar_filha(self):
        return self.__secreto  # Acessa _Filha__secreto

obj = Filha()
print(obj.revelar())       # "valor da base" — não foi sobrescrito!
print(obj.revelar_filha()) # "valor da filha"

# O name mangling pode ser contornado explicitamente:
print(obj._Base__secreto)  # "valor da base" — acesso direto ao nome real
print(obj._Filha__secreto) # "valor da filha"
```

O name mangling é um mecanismo de **proteção contra colisões em herança**, não segurança real. Não use `__atributo` com a expectativa de tornar dados verdadeiramente inacessíveis.

## O decorator @property em profundidade

`@property` transforma um método em um atributo acessível sem parênteses, permitindo validação, computação lazy e manutenção de interface pública estável.

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

    @property
    def celsius(self):
        """Temperatura em graus Celsius."""
        return self._celsius

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

    @celsius.deleter
    def celsius(self):
        print("Resetando temperatura para 0°C")
        self._celsius = 0

    @property
    def fahrenheit(self):
        """Propriedade somente leitura calculada."""
        return self._celsius * 9/5 + 32

    @property
    def kelvin(self):
        return self._celsius + 273.15

temp = Temperatura(25)
print(f"{temp.celsius}°C = {temp.fahrenheit}°F = {temp.kelvin}K")
# 25°C = 77.0°F = 298.15K

temp.celsius = 100
print(temp.fahrenheit)  # 212.0

del temp.celsius        # Resetando temperatura para 0°C
print(temp.celsius)     # 0
```

Uma vantagem importante de `@property`: você pode começar com um atributo público simples e depois adicionar validação sem quebrar a interface. Código que usa `obj.valor` continua funcionando mesmo após a conversão para property.

## Properties vs métodos: quando usar cada um

Use `@property` quando o acesso **parece ser de um atributo** (leitura direta, sem efeitos colaterais pesados). Use métodos quando a operação tem **efeitos colaterais claros**, recebe argumentos ou é visivelmente uma ação.

```python
class Pedido:
    def __init__(self, itens):
        self._itens = itens

    # Property: acesso a dado calculado, parece atributo
    @property
    def total(self):
        return sum(item['preco'] * item['qty'] for item in self._itens)

    # Método: ação explícita com efeito colateral
    def adicionar_item(self, nome, preco, qty):
        self._itens.append({'nome': nome, 'preco': preco, 'qty': qty})

    # Método: ação com retorno significativo
    def aplicar_desconto(self, percentual):
        for item in self._itens:
            item['preco'] *= (1 - percentual / 100)
        return self.total
```

## O protocolo descritor

Descritores são a base dos `@property`, `@staticmethod` e `@classmethod`. Um descritor é qualquer objeto que implemente `__get__`, `__set__` ou `__delete__`. Eles permitem criar atributos com comportamento customizado reutilizável entre várias classes.

```python
class TipadoValidado:
    """Descritor que valida o tipo e range de atributos numéricos."""

    def __init__(self, tipo, minimo=None, maximo=None):
        self.tipo = tipo
        self.minimo = minimo
        self.maximo = maximo

    def __set_name__(self, owner, name):
        self.nome_publico = name
        self.nome_privado = f'_{name}'

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.nome_privado, None)

    def __set__(self, obj, valor):
        if not isinstance(valor, self.tipo):
            raise TypeError(
                f"{self.nome_publico} deve ser {self.tipo.__name__}, "
                f"recebeu {type(valor).__name__}"
            )
        if self.minimo is not None and valor < self.minimo:
            raise ValueError(f"{self.nome_publico} mínimo é {self.minimo}")
        if self.maximo is not None and valor > self.maximo:
            raise ValueError(f"{self.nome_publico} máximo é {self.maximo}")
        setattr(obj, self.nome_privado, valor)

class Produto:
    nome = TipadoValidado(str)
    preco = TipadoValidado((int, float), minimo=0)
    estoque = TipadoValidado(int, minimo=0, maximo=10000)

    def __init__(self, nome, preco, estoque):
        self.nome = nome
        self.preco = preco
        self.estoque = estoque

p = Produto("Notebook", 3500.0, 10)
# p.preco = -100   # ValueError: preco mínimo é 0
# p.estoque = "5"  # TypeError: estoque deve ser int
```

## __getattr__ vs __getattribute__

`__getattr__` é chamado **apenas quando o atributo não é encontrado** pelos meios normais — ideal para atributos dinâmicos. `__getattribute__` é chamado **sempre** para qualquer acesso de atributo — use com extremo cuidado para evitar recursão infinita.

```python
class Configuracao:
    """Classe que permite acesso a configurações com fallback para padrão."""

    def __init__(self, dados):
        # Usar object.__setattr__ para evitar recursão no __setattr__
        object.__setattr__(self, '_dados', dados)
        object.__setattr__(self, '_padroes', {
            'timeout': 30,
            'retries': 3,
            'debug': False,
        })

    def __getattr__(self, nome):
        # Chamado apenas quando o atributo normal não existe
        dados = object.__getattribute__(self, '_dados')
        padroes = object.__getattribute__(self, '_padroes')
        if nome in dados:
            return dados[nome]
        if nome in padroes:
            return padroes[nome]
        raise AttributeError(f"Configuração '{nome}' não encontrada")

    def __setattr__(self, nome, valor):
        dados = object.__getattribute__(self, '_dados')
        dados[nome] = valor

cfg = Configuracao({'host': 'localhost', 'porta': 5432})
print(cfg.host)     # localhost
print(cfg.porta)    # 5432
print(cfg.timeout)  # 30 (fallback para padrão)
print(cfg.debug)    # False (fallback para padrão)
```

## __slots__ e encapsulamento

`__slots__` controla quais atributos um objeto pode ter, evitando a adição acidental de novos atributos e reduzindo o consumo de memória:

```python
class Ponto:
    __slots__ = ('x', 'y')

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

p = Ponto(1, 2)
print(p.x)   # 1
# p.z = 3   # AttributeError: 'Ponto' object has no attribute 'z'
# Sem __dict__, sem adição de atributos não declarados

# Com __slots__, não há __dict__:
# print(p.__dict__)  # AttributeError
```

## Dataclass e visibilidade de campos

Com `@dataclass`, a visibilidade segue as mesmas convenções:

```python
from dataclasses import dataclass, field

@dataclass
class Usuario:
    nome: str
    email: str
    _senha_hash: str = field(default="", repr=False, compare=False)
    __id: int = field(default=0, repr=False, compare=False, init=False)

    def definir_senha(self, senha):
        import hashlib
        self._senha_hash = hashlib.sha256(senha.encode()).hexdigest()

    def verificar_senha(self, senha):
        import hashlib
        return self._senha_hash == hashlib.sha256(senha.encode()).hexdigest()
```

## Python vs Java: filosofia de encapsulamento

Em Java, `private` realmente impede o acesso externo. Em Python, a filosofia é diferente: o código-fonte é sempre acessível, e tentar criar barreiras intransponíveis vai contra a filosofia da linguagem.

Python aposta na **transparência e convenção**: `_atributo` diz "prefiro que você não use isso diretamente", `__atributo` diz "este nome é específico desta classe para evitar colisões". A confiança no desenvolvedor é intencional — facilita testes, inspeção e debugging.

Isso não significa que encapsulamento é ignorado em Python. Significa que é aplicado através de **design de API claro**, não de restrições de acesso impostas pela linguagem.

## Erros comuns

Nunca use atributos mutáveis (listas, dicionários) como padrão em `__init__` sem inicializá-los individualmente. Outro erro frequente é usar `__atributo` pensando que é "realmente privado" — ele ainda é acessível via `_Classe__atributo`. Evite também criar properties para todos os atributos de forma reflexiva — use-as apenas quando há lógica de validação ou computação.

## Quando usar cada nível de acesso?

Use `atributo` público para dados que fazem parte da interface principal do objeto. Use `_atributo` para implementação interna que pode mudar. Use `__atributo` para evitar colisões de nomes em hierarquias de herança. Use `@property` quando precisar de validação ou computação no momento do acesso.

## Termos Relacionados

- [Classe](/glossario/classe/) - Base da programação orientada a objetos
- [Herança](/glossario/heranca/) - Reutilização de código entre classes
- [Polimorfismo](/glossario/polimorfismo/) - Múltiplas formas para mesma interface
- [Decorators](/glossario/decorators/) - @property é um decorator
