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

Métodos dunder: integrando à linguagem

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

O que você vai aprender

  • Diferenciar __repr__ de __str__ e implementar os dois.
  • Definir igualdade e ordem com __eq__ e __lt__.
  • Fazer um objeto se comportar como coleção com __len__ e __getitem__.
  • Tornar um objeto chamável com __call__.

__repr__ contra __str__

Os métodos dunder são a ponte entre as suas classes e a sintaxe do Python. Sem eles, um objeto seu impresso mostra algo cru, como o nome da classe e um endereço de memória. Os dois primeiros a dominar são __repr__ e __str__. O __repr__ é a representação técnica, pensada para quem programa: idealmente, mostra algo que descreve o objeto sem ambiguidade, muitas vezes parecido com o código que o criaria. O __str__ é a representação amigável, pensada para o usuário final, mais limpa e legível. Quando você usa print, o Python prefere o __str__; quando você inspeciona no console ou dentro de uma lista, ele usa o __repr__.

class Dinheiro:
    def __init__(self, reais):
        self.reais = reais

    def __repr__(self):
        return f"Dinheiro({self.reais})"      # tecnico, para dev

    def __str__(self):
        return f"R$ {self.reais:.2f}"          # amigavel, para usuario

d = Dinheiro(12.5)
print(d)          # R$ 12.50   (usa __str__)
print(repr(d))    # Dinheiro(12.5)   (usa __repr__)
print([d])        # [Dinheiro(12.5)]  (lista usa __repr__)

print prefere __str__; console e coleções usam __repr__. Defina ao menos __repr__.

Comparar com __eq__ e __lt__

Por padrão, dois objetos diferentes nunca são iguais, mesmo que carreguem os mesmos dados, porque o Python compara identidade, não conteúdo. Para mudar isso, você define __eq__, que decide quando dois objetos devem ser considerados iguais, comparando os atributos que importam. Com __eq__ implementado, o operador de igualdade passa a olhar o conteúdo. Já o __lt__, de less than, define quando um objeto é menor que outro, e é ele que a função sorted usa para ordenar. Basta implementar __lt__ para tornar seus objetos ordenáveis; o Python cuida do resto da ordenação a partir dessa única comparação.

class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    def __eq__(self, outro):
        return self.preco == outro.preco

    def __lt__(self, outro):
        return self.preco < outro.preco

    def __repr__(self):
        return f"{self.nome} (R$ {self.preco})"

produtos = [Produto("A", 30), Produto("B", 10), Produto("C", 20)]
print(sorted(produtos))   # ordena por preco usando __lt__
# [B (R$ 10), C (R$ 20), A (R$ 30)]
print(Produto("X", 10) == Produto("Y", 10))   # True, mesmo preco

__eq__ compara por conteúdo; __lt__ dá a ordem, e sorted a usa automaticamente.

Um cuidado: ao definir __eq__, você costuma querer também o __hash__ se pretende usar o objeto em conjuntos ou como chave de dicionário, um tema que o módulo de dataclasses trata com mais conforto. E, ao implementar comparações, mantenha a coerência: se __eq__ olha o preço, faz sentido que __lt__ também olhe. Comparações que se contradizem geram ordenações estranhas e bugs difíceis. A biblioteca padrão tem um auxílio, o functools.total_ordering, que gera as demais comparações a partir de __eq__ e __lt__, poupando você de escrever todas na mão.

Coleções e objetos chamáveis

Outro grupo de dunders faz o seu objeto se comportar como uma coleção. O __len__ responde a len(obj), devolvendo um tamanho. O __getitem__ permite a indexação obj[i] e, de quebra, torna o objeto iterável, porque o Python sabe percorrer algo que responde a índices crescentes. Com esses dois métodos, uma classe sua passa a funcionar em len, em laços for e em fatiamento, integrando-se ao vocabulário da linguagem como se fosse uma lista. É assim que bibliotecas criam estruturas próprias que você usa com a mesma naturalidade dos tipos nativos.

class Baralho:
    def __init__(self):
        self.cartas = [f"carta {i}" for i in range(1, 6)]

    def __len__(self):
        return len(self.cartas)

    def __getitem__(self, posicao):
        return self.cartas[posicao]

b = Baralho()
print(len(b))       # 5  (usa __len__)
print(b[0])         # carta 1  (usa __getitem__)
for carta in b:     # iteravel de graca, gracas a __getitem__
    print(carta)

__len__ e __getitem__ fazem o objeto responder a len, indexação e laços for.

Por fim, o __call__ é um dos mais curiosos: ele faz uma instância ser chamada como se fosse uma função. Depois de implementar __call__, você escreve obj(argumentos) e o Python executa esse método. Isso é útil para objetos que guardam configuração e agem como funções especializadas, os chamados objetos chamáveis. Um multiplicador que lembra o fator, um contador que soma a cada chamada, um validador configurado uma vez e aplicado muitas. O objeto tem estado, como uma classe, e se usa como função, o melhor dos dois mundos.

class Multiplicador:
    def __init__(self, fator):
        self.fator = fator

    def __call__(self, valor):
        return valor * self.fator   # chamar o objeto executa isto

dobrar = Multiplicador(2)
triplicar = Multiplicador(3)
print(dobrar(10))     # 20  (dobrar e um objeto, mas se chama como funcao)
print(triplicar(10))  # 30

__call__ torna o objeto chamável: dobrar(10) executa o método, guardando estado (o fator).

Teste rápido

Qual a diferença entre __repr__ e __str__?

Perguntas frequentes

Se eu definir só __str__, o que acontece na inspeção do objeto?
A inspeção técnica, no console ou dentro de listas, continua usando a representação crua padrão, com nome da classe e endereço, porque falta o __repr__. Por isso a recomendação é sempre ter um bom __repr__: ele serve de fallback para o print quando não há __str__, mas o contrário não vale.
Preciso definir todas as comparações, como maior e menor ou igual?
Não necessariamente. Com __eq__ e __lt__ você já cobre igualdade, ordenação com sorted e boa parte dos casos. Para gerar as demais comparações automaticamente a partir dessas duas, existe o decorador functools.total_ordering, que evita escrever cada operador na mão de forma coerente.
Por que ao definir __eq__ eu deveria pensar no __hash__?
Porque objetos usados em conjuntos ou como chave de dicionário precisam de um hash coerente com a igualdade: dois objetos iguais devem ter o mesmo hash. Ao mudar o __eq__, o comportamento de hash padrão pode ficar inconsistente. As dataclasses, do módulo seguinte, cuidam disso de forma automática e mais segura.
Implementar __getitem__ realmente torna o objeto iterável?
Sim, pelo protocolo antigo de iteração: o Python percorre o objeto pedindo obj[0], obj[1] e assim por diante, até um IndexError. Para controle fino da iteração, existe o __iter__, mas para muitas coleções simples o __getitem__ já entrega o laço for de graça, junto com a indexação.
Para que serve tornar um objeto chamável com __call__?
Para criar objetos que guardam estado e se usam como funções. Um validador configurado uma vez e aplicado muitas, um multiplicador que lembra o fator, um contador que soma a cada chamada. Você tem a memória de uma classe com a sintaxe de uma função, útil quando a função precisa carregar configuração entre chamadas.

Fontes

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