Se você já desenvolveu aplicações Python de médio ou grande porte, provavelmente já enfrentou problemas com consumo excessivo de memória. Cada objeto em Python carrega um dicionário (__dict__) que armazena seus atributos de forma dinâmica — mas essa flexibilidade tem um custo elevado. É aqui que __slots__ entra como uma solução elegante e poderosa para otimização.

Neste guia completo, você vai aprender o que são __slots__, como eles funcionam internamente, quando utilizá-los e quais cuidados tomar. Tudo com exemplos práticos e benchmarks reais para que você possa aplicar imediatamente nos seus projetos.

O Problema: Consumo de Memória em Classes Python

Para entender o valor dos __slots__, primeiro precisamos compreender como o Python gerencia atributos de instância. Por padrão, toda classe em Python armazena seus atributos em um dicionário chamado __dict__. Esse dicionário permite adicionar, remover e modificar atributos dinamicamente — uma característica que torna Python tão flexível.

O problema é que dicionários são estruturas de dados pesadas. Cada entrada em um dicionário inclui a chave, o valor, o hash da chave e metadados internos. Quando você tem milhares ou milhões de objetos, o __dict__ de cada um deles consome uma quantidade significativa de memória. Como explica a documentação oficial do Python, a declaração de __slots__ permite substituir o __dict__ por uma estrutura de armazenamento mais eficiente.

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

obj = SemSlots(10, 20) print(obj.dict) # {'x': 10, 'y': 20} print(sys.getsizeof(obj)) # 56 bytes (sem contar o dict)

O dicionário __dict__ ocupa cerca de 120 bytes adicionais para um objeto simples com dois atributos, e esse valor cresce conforme mais atributos são adicionados. Em aplicações que criam muitos objetos — como jogos, simulações científicas, processamento de dados em larga escala ou servidores de alta performance — esse overhead se torna um gargalo significativo.

O Que São __slots__?

__slots__ é um atributo especial de classe que define explicitamente quais atributos de instância uma classe pode ter. Quando você declara __slots__, o Python deixa de criar o dicionário __dict__ para cada instância e passa a alocar apenas o espaço necessário para os atributos listados. O Python Wiki descreve essa técnica como uma forma de reduzir o consumo de memória em até 50% ou mais.

class ComSlots:
    __slots__ = ('x', 'y')
def __init__(self, x, y):
    self.x = x
    self.y = y

obj2 = ComSlots(10, 20)

print(obj2.dict) # AttributeError: 'ComSlots' object has no attribute 'dict'

print(sys.getsizeof(obj2)) # 48 bytes (total, sem dict extra)

A sintaxe é simples: basta definir uma tupla (ou lista) com os nomes dos atributos permitidos. Cada instância da classe passa a armazenar esses valores em um array compacto de tamanho fixo, em vez de um dicionário completo.

Como __slots__ Funcionam Internamente

Para realmente dominar __slots__, é importante entender o mecanismo interno. Quando o Python compila uma classe que define __slots__, ele cria descritores para cada nome listado. Esses descritores gerenciam o acesso aos valores armazenados em um slot numerado dentro de um array interno da instância.

Isso significa que o acesso a atributos em classes com __slots__ é não apenas mais econômico em memória, mas também mais rápido. A documentação oficial de classes do Python oferece uma visão geral excelente desse comportamento.

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

p = Ponto(1, 2, 3) print(p.x) # 1

p.w = 4 # AttributeError: 'Ponto' object has no attribute 'w'

Além de economizar memória, a restrição de criar novos atributos fora de __slots__ funciona como uma proteção contra erros de digitação. Se você acidentalmente escrever self.x = 1 em vez de self.x = 1, o Python levanta um AttributeError imediatamente, em vez de criar silenciosamente um atributo novo e incorreto.

Exemplos Práticos

Vamos explorar cenários reais onde __slots__ faz diferença significativa.

Sistema de Partículas para Jogos

Em um jogo 2D, você pode ter milhares de partículas na tela simultaneamente. Cada partícula precisa armazenar posição, velocidade, cor, tempo de vida e outros atributos. Sem __slots__, a memória consumida por essas partículas pode rapidamente sair do controle.

class Particula:
    __slots__ = ('x', 'y', 'vx', 'vy', 'cor', 'vida', 'tamanho')
def __init__(self, x, y, vx, vy, cor, vida, tamanho):
    self.x = x
    self.y = y
    self.vx = vx
    self.vy = vy
    self.cor = cor
    self.vida = vida
    self.tamanho = tamanho

particulas = [Particula(i, i*2, 1, -1, 'azul', 100, 3) for i in range(100000)]

Com __slots__, cada partícula ocupa cerca de 80 bytes. Sem __slots__, cada uma ocuparia aproximadamente 200 bytes ou mais. Para 100 mil partículas, a economia é de cerca de 12 MB. O memory-profiler é uma ferramenta útil para medir exatamente essa diferença nos seus projetos.

Registros de Dados (Data Records)

Imagine que você está processando um arquivo CSV com milhões de linhas e representa cada linha como um objeto Python. Usar __slots__ reduz drasticamente o consumo de memória.

class RegistroVenda:
    __slots__ = ('produto', 'quantidade', 'preco', 'data', 'vendedor')
def __init__(self, produto, quantidade, preco, data, vendedor):
    self.produto = produto
    self.quantidade = quantidade
    self.preco = preco
    self.data = data
    self.vendedor = vendedor</code></pre>

Essa abordagem é especialmente útil quando combinada com a leitura sob demanda de arquivos grandes, onde cada linha é processada e mantida em memória por um curto período. A economia de memória se acumula e pode significar a diferença entre o programa rodar ou estourar o limite de RAM disponível.

Benchmarks de Memória e Performance

Vamos comparar objetivamente classes com e sem __slots__ usando a função sys.getsizeof e um teste simples de velocidade de acesso.

import sys
import time

class ClasseSemSlots: def init(self, a, b, c): self.a = a self.b = b self.c = c

class ClasseComSlots: slots = ('a', 'b', 'c') def init(self, a, b, c): self.a = a self.b = b self.c = c

Comparação de memória

obj1 = ClasseSemSlots(1, 2, 3) obj2 = ClasseComSlots(1, 2, 3)

print(f"Sem slots: {sys.getsizeof(obj1)} bytes") print(f"Com slots: {sys.getsizeof(obj2)} bytes")

O dict adiciona mais memória

dict_size = sys.getsizeof(obj1.dict) print(f"dict extra: {dict_size} bytes")

Os resultados típicos mostram que objetos com __slots__ consomem de 40 a 60% menos memória. Em testes de velocidade de acesso a atributos, a diferença também é perceptível: objetos com __slots__ são geralmente 10 a 15% mais rápidos na leitura e escrita de atributos.

Quando Usar __slots__

__slots__ não é uma solução universal. Existem cenários específicos onde ele brilha e outros onde seu uso é desnecessário ou até prejudicial. O Stack Overflow tem discussões excelentes sobre os prós e contras de usar __slots__.

Use __slots__ quando:

  • Você cria milhares ou milhões de objetos da mesma classe
  • Cada objeto tem um conjunto fixo e bem definido de atributos
  • A memória é um recurso crítico (jogos, sistemas embarcados, servidores com alto throughput)
  • Performance de acesso a atributos é importante
  • Você quer prevenir a criação acidental de novos atributos

Evite __slots__ quando:

  • Você precisa adicionar atributos dinamicamente
  • Suas classes fazem uso intenso de herança múltipla
  • Você depende de weak references sem implementar __weakref__ nos slots
  • O número de objetos da classe é pequeno (centenas ou poucos milhares)

Limitações e Cuidados

Apesar dos benefícios, __slots__ impõe algumas limitações importantes que você precisa conhecer antes de adotá-lo em seus projetos.

1. Sem dicionário de instância: Como mencionado, objetos com __slots__ não têm __dict__. Isso significa que você não pode adicionar novos atributos dinamicamente após a criação do objeto. Se você tentar, receberá um AttributeError.

2. Sem weak references por padrão: Objetos com __slots__ não suportam weakref a menos que você inclua '__weakref__' na tupla de slots explicitamente.

class ComWeakRef:
    __slots__ = ('x', '__weakref__')
    def __init__(self, x):
        self.x = x

3. Herança: Subclasses de classes com __slots__ também precisam definir seus próprios __slots__ para otimizar o uso de memória. Se uma subclasse não definir __slots__, ela terá um __dict__ mesmo que a classe pai tenha __slots__. O modelo de dados do Python explica esse comportamento em detalhes.

class Base:
    __slots__ = ('a',)

class Derivada(Base): slots = ('b',)

d = Derivada() d.a = 1 # OK d.b = 2 # OK

d.c = 3 # AttributeError

__slots__ e Herança: Boas Práticas

A herança com __slots__ requer atenção redobrada. Quando você herda de uma classe que usa __slots__, a subclasse deve declarar seus próprios __slots__ incluindo apenas os atributos que ela adiciona — não os da classe pai. O Python gerencia automaticamente o espaço para os atributos de todos os níveis da hierarquia.

class Veiculo:
    __slots__ = ('marca', 'modelo', 'ano')
def __init__(self, marca, modelo, ano):
    self.marca = marca
    self.modelo = modelo
    self.ano = ano

class Carro(Veiculo): slots = ('portas',)

def __init__(self, marca, modelo, ano, portas):
    super().__init__(marca, modelo, ano)
    self.portas = portas</code></pre>

Uma armadilha comum é esquecer de definir __slots__ na subclasse. Nesse caso, a subclasse terá um __dict__ e não aproveitará a otimização de memória, mesmo que a classe pai use __slots__.

# ERRADO: Sem __slots__ na subclasse
class Carro2(Veiculo):
    def __init__(self, marca, modelo, ano, portas):
        super().__init__(marca, modelo, ano)
        self.portas = portas

c2 = Carro2('VW', 'Gol', 2024, 4) print(hasattr(c2, 'dict')) # True! Tem dict mesmo com pai usando slots

__slots__ e o Protocolo de Descritores

Internamente, cada nome em __slots__ cria um descritor na classe. Descritores são objetos que gerenciam o acesso a atributos através dos métodos __get__, __set__ e __delete__. Isso significa que você pode combinar __slots__ com @property para ter controle fino sobre a entrada e saída de dados.

class Usuario:
    __slots__ = ('_nome', '_email')
def __init__(self, nome, email):
    self._nome = nome
    self._email = email

@property
def nome(self):
    return self._nome

@nome.setter
def nome(self, valor):
    if not valor.strip():
        raise ValueError("Nome não pode ser vazio")
    self._nome = valor</code></pre>

Essa combinação permite que você tenha a eficiência de __slots__ sem abrir mão das boas práticas de encapsulamento. Para mais detalhes sobre o decorador @property, consulte nosso guia completo sobre o tema. [link_interno_2]

Alternativas ao __slots__

Dependendo do seu caso de uso, outras estruturas podem ser mais adequadas que __slots__:

namedtuple: Cria classes imutáveis com acesso a atributos por nome. Eficiente em memória, mas não permite métodos personalizados com facilidade.

dataclass: A partir do Python 3.7, o módulo dataclasses permite criar classes com sintaxe enxuta. Você pode combinar dataclass com __slots__ usando slots=True a partir do Python 3.10.

from dataclasses import dataclass

@dataclass(slots=True) class Configuracao: host: str porta: int timeout: float

dict simples ou types.SimpleNamespace: Para objetos que são meros recipientes de dados, um dicionário ou SimpleNamespace pode ser suficiente. O tutorial do Real Python sobre __slots__ compara essas alternativas em detalhes.

Arrays NumPy: Para dados numéricos homogêneos, arrays do NumPy são extremamente eficientes em memória e muito mais rápidos que listas de objetos Python.

Erros Comuns com __slots__

Aqui estão os erros mais frequentes que desenvolvedores cometem ao usar __slots__:

1. Esquecer de incluir '__weakref__' e '__dict__': Se você precisa de weak references ou dicionário de instância, inclua-os explicitamente nos slots.

class Flexivel:
    __slots__ = ('x', '__weakref__', '__dict__')

f = Flexivel() f.x = 1 f.y = 2 # OK, porque dict está nos slots

2. Usar __slots__ com herança múltipla: A herança múltipla com __slots__ funciona apenas quando todas as classes base definem exatamente o mesmo conjunto de slots. Caso contrário, o Python levanta um erro.

3. Tentar serializar objetos com pickle: Objetos com __slots__ podem ser serializados com pickle, mas você precisa garantir que a classe esteja disponível no momento da desserialização. Classes que dependem de __slots__ geralmente funcionam bem com pickle, desde que você inclua __getstate__ e __setstate__ se necessário.

A documentação do glossário Python oferece definições precisas e links para recursos adicionais sobre __slots__ e outros mecanismos internos da linguagem.

Dicas Avançadas

Para desenvolvedores que querem ir além, aqui estão algumas técnicas avançadas com __slots__:

Introspecção com dir()

Mesmo sem __dict__, você ainda pode inspecionar objetos com dir(). A função mostra todos os atributos disponíveis, incluindo os definidos em __slots__.

Slots Dinâmicos

Você pode modificar __slots__ após a definição da classe? Tecnicamente não — é um atributo de classe definido em tempo de compilação. No entanto, você pode usar __dict__ como um slot adicional para permitir atribuição dinâmica controlada.

Comparação com Linguagens Compiladas

O mecanismo de __slots__ é semelhante a como linguagens como C++ ou Java armazenam atributos de objeto: um bloco contíguo de memória com offsets fixos. Isso aproxima Python do desempenho de linguagens de mais baixo nível em cenários específicos, sem perder a sintaxe expressiva da linguagem.

Conclusão

__slots__ é uma ferramenta poderosa no arsenal de qualquer desenvolvedor Python que se preocupa com performance e eficiência de memória. Quando usado corretamente, pode reduzir o consumo de memória em mais de 50% e acelerar o acesso a atributos de forma significativa. Não é uma solução para todos os casos, mas nos cenários certos — objetos em grande escala, jogos, processamento de dados — faz uma diferença transformadora.

Comece analisando seus projetos atuais: identifique classes que geram muitas instâncias com atributos fixos e experimente adicionar __slots__. Meça o consumo de memória antes e depois, e comprove os benefícios na prática. O investimento em entender esse mecanismo interno do Python vai tornar você um desenvolvedor mais completo e preparado para desafios de performance.

Para continuar seus estudos, recomendamos nosso guia completo sobre programação orientada a objetos em Python. [link_interno_1]


Referências e Recursos