Módulo 10 - Orientação a objetos avançada

Composição, herança e __slots__

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Distinguir composição de herança com a regra é um contra tem um.
  • Reconhecer quando a herança modela mal um relacionamento.
  • Usar __slots__ para economizar memória.
  • Entender o que __slots__ cobra em troca da economia.

É um contra tem um

Herança é atraente porque parece resolver reúso de código de graça: herde e ganhe os métodos da base. Mas ela cria um laço forte entre a classe filha e a mãe, e usada onde não cabe gera hierarquias confusas. A pergunta que orienta a decisão é simples e velha conhecida: o novo tipo é um subtipo do outro, ou apenas contém o outro? Se um Gato é um Animal, a herança modela bem: o gato realmente é um caso particular de animal. Mas se um Carro tem um Motor, herdar Carro de Motor seria absurdo, porque o carro não é um tipo de motor; ele tem um. Aí a relação certa é composição.

Herança (é um)

  • Gato é um Animal
  • Retângulo é uma Forma
  • A filha é um caso particular da mãe
  • Reúsa por especialização

Composição (tem um)

  • Carro tem um Motor
  • Pedido tem uma lista de Itens
  • A classe contém outra como componente
  • Reúsa por delegação

Composição na prática

Na composição, em vez de herdar, a classe guarda um objeto de outra como atributo e delega a ele o que for da sua alçada. O Carro tem um Motor guardado em self.motor e, quando precisa ligar, chama self.motor.ligar(). A vantagem é o baixo acoplamento: o carro depende apenas da interface do motor, não da sua estrutura interna, e você pode trocar o tipo de motor sem reescrever o carro. É mais flexível que a herança, que amarra a filha à implementação da mãe. Por isso um conselho clássico de projeto é preferir composição a herança quando ambas parecem possíveis; a herança fica reservada aos casos claros de é um.

class Motor:
    def __init__(self, potencia):
        self.potencia = potencia

    def ligar(self):
        return f"motor de {self.potencia}cv ligado"

class Carro:
    def __init__(self, modelo, potencia):
        self.modelo = modelo
        self.motor = Motor(potencia)   # Carro TEM UM Motor (composicao)

    def ligar(self):
        return f"{self.modelo}: {self.motor.ligar()}"

c = Carro("Sedan", 120)
print(c.ligar())   # Sedan: motor de 120cv ligado

Carro compõe um Motor e delega a ele: baixo acoplamento, fácil de trocar o motor.

__slots__ e a economia de memória

O último recurso do módulo é uma otimização. Por padrão, cada instância no Python guarda seus atributos em um dicionário interno, o __dict__, o que dá muita flexibilidade: você pode acrescentar um atributo novo a um objeto a qualquer momento. Essa flexibilidade tem um custo de memória por instância, irrelevante para poucos objetos, mas pesado quando você cria milhões. O __slots__ é a resposta. Ao declarar __slots__ com a lista dos atributos permitidos, você diz ao Python para reservar espaço fixo para eles e dispensar o dicionário interno. O ganho de memória é substancial e, muitas vezes, o acesso aos atributos fica até um pouco mais rápido.

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

class PontoSlots:
    __slots__ = ("x", "y")   # so estes atributos, sem __dict__

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

p = PontoSlots(1, 2)
print(p.x, p.y)     # 1 2  (funciona normalmente)
# p.z = 3           # AttributeError: nao esta em __slots__

__slots__ fixa os atributos e remove o __dict__ por instância, economizando memória.

A economia vem com um preço, e é justo conhecê-lo antes de sair usando __slots__ em tudo. Como não há mais o dicionário interno, você não pode acrescentar atributos que não estejam na lista: tentar fazê-lo levanta um erro. Isso é ótimo quando você quer justamente travar a estrutura, mas atrapalha se o seu design conta com atributos dinâmicos. Há também sutilezas com herança e com alguns recursos que esperam o __dict__. A regra prática é: use __slots__ quando tiver muitas instâncias de uma classe simples e de estrutura fixa, e o consumo de memória for medido como um problema real. Fora desse cenário, a flexibilidade padrão costuma valer mais que a economia.

Teste rápido

O que o __slots__ oferece e o que ele cobra em troca?

Perguntas frequentes

Qual é a regra rápida para escolher entre herança e composição?
Use a frase de teste. Se é um descreve a relação, como um Gato é um Animal, a herança cabe. Se tem um descreve melhor, como um Carro tem um Motor, use composição. Na dúvida, prefira composição: ela acopla menos e deixa o código mais flexível para mudar depois.
Por que preferir composição a herança quando ambas parecem servir?
Porque a herança amarra a filha à implementação da mãe, e mudanças na base podem quebrar as filhas. A composição depende só da interface do componente, então você pode trocar ou ajustar o componente sem reescrever quem o usa. Esse acoplamento menor torna o sistema mais fácil de evoluir.
Quando o __slots__ realmente vale a pena?
Quando você cria muitas instâncias, na casa dos milhares ou milhões, de uma classe simples e de estrutura fixa, e mediu que o consumo de memória é um problema. Nesse cenário, a economia é expressiva. Para poucos objetos ou classes que precisam de atributos dinâmicos, o __slots__ atrapalha mais do que ajuda.
Com __slots__ ainda posso usar property e métodos?
Pode. O __slots__ restringe apenas os atributos de dados por instância, não os métodos nem as properties, que pertencem à classe. Um detalhe: se você quiser uma property com o mesmo nome de um slot, há conflito; a saída comum é guardar o dado em um slot com nome interno e expor a property sobre ele.
Dataclasses combinam com __slots__?
Sim. Versões modernas do Python permitem gerar dataclasses com slots, unindo a conveniência da dataclass com a economia de memória. Como as dataclasses são o tema do próximo bloco do curso, vale ter em mente essa combinação para quando precisar de muitos objetos leves e bem estruturados ao mesmo tempo.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.