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__.
Ouvir o resumo desta aula
Um recap de cerca de 2 minutos na voz do Valim, para ouvir no trânsito ou na academia.
Ler a transcrição do resumo
Resumo da aula: Métodos dunder: integrando à linguagem.
Os objetivos desta aula. 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__.
Veja o essencial, parte por parte.
__repr__ contra __str__. __repr__ é a representação técnica, para desenvolvedores; __str__ é a amigável, para o usuário.
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.
Coleções e objetos chamáveis. Outro grupo de dunders faz o seu objeto se comportar como uma coleção.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
__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.