
Passo 1 de 7
O contrato que dicionários e conjuntos exigem
Entenda as garantias que permitem usar objetos com segurança em dicionários e conjuntos.
Trilha de aprendizado · Nível 11 · Tutorial 3
Ao concluir, você poderá decidir se uma classe deve aceitar hash e implementar esse comportamento sem comprometer consultas em dicionários e conjuntos.
O contrato que dicionários e conjuntos exigem
Entenda as garantias que permitem usar objetos com segurança em dicionários e conjuntos. 3 min
Quando uma classe aceita hash
Preveja se instâncias de uma classe podem ser usadas em operações que exigem hash a partir de suas definições de igualdade e hash. 3 min
Separar estado mutável de identidade de valor
Decida quando uma alteração de estado é compatível com o uso de uma instância como chave de dicionário ou elemento de conjunto. 3 min
Compor o hash com os campos certos
Implemente um hash coerente com a igualdade de valor e mantenha esse contrato estável. 4 min
Respeitar os limites de uso do hash
Use hash para buscas em memória, não como identificador salvo ou número permanente. 2 min
Testar o contrato pelo uso real
Execute um script autocontido para verificar o comportamento de objetos equivalentes em dicionários e conjuntos. 4 min
Aplicação final: escolher, corrigir e validar
Aplique o contrato de igualdade e hash em dois modelos e valide o comportamento em dicionários e conjuntos. 3 min

Passo 1 de 7
Entenda as garantias que permitem usar objetos com segurança em dicionários e conjuntos.
A chamada hash(objeto) usa o comportamento __hash__ do objeto e produz um inteiro. Esse inteiro funciona como uma pista para dicionários e conjuntos encontrarem rapidamente uma região provável onde procurar.
Para um objeto ser hashável, seu hash deve permanecer estável durante toda a vida em que ele puder ser usado como chave de dicionário ou elemento de conjunto.
A busca usa duas etapas conceituais: primeiro o hash reduz a área de procura; depois a igualdade decide se o objeto encontrado é realmente o procurado.

O hash direciona a busca; == confirma a correspondência.
Exemplo
Se a == b é verdadeiro para dois objetos hasháveis, então hash(a) == hash(b) também precisa ser verdadeiro.
Em forma curta:
a == b → hash(a) == hash(b)
O sentido inverso não é exigido. Dois objetos podem ter o mesmo hash e ainda assim serem diferentes.
Dica
Use o hash para orientar uma consulta, não para concluir igualdade. Um hash igual é apenas uma possibilidade de correspondência; a comparação de igualdade ainda é necessária.
Uma colisão acontece quando objetos diferentes produzem o mesmo hash. Isso não viola o contrato: o dicionário ou conjunto compara os candidatos com igualdade para distingui-los.
Portanto, hashes não precisam ser únicos. O requisito é que objetos iguais nunca recebam hashes diferentes.
Mesmo recebendo a mesma pista de hash, dois objetos distintos continuam separados porque a etapa seguinte verifica igualdade.

Colisão: mesmo hash; igualdade falsa; objetos distintos.
Se dois objetos hasháveis são iguais por ==, eles devem produzir o mesmo valor em hash().
Se hash(a) == hash(b), então a == b necessariamente é verdadeiro.

Passo 2 de 7
Preveja se instâncias de uma classe podem ser usadas em operações que exigem hash a partir de suas definições de igualdade e hash.
Uma classe que mantém os comportamentos padrão de object compara instâncias pela identidade. Nesse caso, ela também é hashável: hash(objeto) devolve um inteiro apropriado para consultas em dicionários e conjuntos.
Não dependa, porém, de hash(objeto) == id(objeto) nem suponha que cada instância tenha um hash exclusivo. O contrato só exige coerência com a igualdade.
class Sessao:
pass
primeira = Sessao()
segunda = Sessao()
print(primeira == segunda) # False: são instâncias distintas
print(hash(primeira)) # inteiro
print({primeira, segunda}) # as duas instâncias podem coexistir
Com igualdade padrão, cada instância é comparada com base em sua própria identidade.
Ao definir __eq__ para criar igualdade de valor e não fornecer __hash__, o Python torna a classe não hashável automaticamente. Isso evita que uma igualdade personalizada seja combinada por acidente com um hash herdado incompatível.
Assim, hash(instancia), usar a instância como chave ou adicioná-la a um conjunto causam TypeError.
class Codigo:
def __init__(self, valor):
self.valor = valor
def __eq__(self, outro):
if not isinstance(outro, Codigo):
return NotImplemented
return self.valor == outro.valor
item = Codigo("A-17")
print(hash(item)) # TypeError: unhashable type: 'Codigo'
Personalizar __eq__ sem definir __hash__ bloqueia o uso em estruturas baseadas em hash.
Escreva __hash__ = None para declarar explicitamente que as instâncias não são hasháveis. Essa escolha também produz TypeError nas operações que exigem hash.
Evite a aparente correção __hash__ = object.__hash__ em uma classe com igualdade de valor. Ela recupera um hash ligado à identidade; duas instâncias diferentes podem ser iguais e ainda ter hashes diferentes, quebrando o contrato.
Atenção
object.__hash__ só é compatível quando a igualdade também segue a identidade. Se __eq__ diz que objetos distintos podem ser iguais, atribuir object.__hash__ não é uma solução geral.
class Rascunho:
__hash__ = None
rascunho = Rascunho()
# hash(rascunho) # TypeError
# {rascunho} # TypeError
# {rascunho: "aberto"} # TypeErrorRelacione cada forma de classe ao resultado esperado para hash(instancia).
Toque em um item e depois no par correspondente.

Passo 3 de 7
Decida quando uma alteração de estado é compatível com o uso de uma instância como chave de dicionário ou elemento de conjunto.
Uma instância pode ter atributos mutáveis e ainda ser hashável — desde que esses atributos não definam sua igualdade nem influenciem seu hash.
Separe mentalmente dois grupos:
__eq__, precisam permanecer estáveis enquanto o objeto estiver em um dicionário ou conjunto.A pergunta decisiva não é “o objeto muda?”, mas sim: “os campos que definem seu valor podem mudar?”
Observe quais alterações afetam a segurança da chave.

Alterar metadados pode ser seguro; alterar campos usados na igualdade ou no hash compromete o contrato.
Dicionários e conjuntos usam o hash para orientar onde procurar e depois verificam a igualdade. Se um campo de identidade muda após a inserção, o objeto pode passar a produzir outro hash ou deixar de ser igual à instância equivalente usada na consulta.
O resultado não deve ser tratado como uma falha previsível única: uma consulta pode deixar de localizar a chave, uma remoção pode falhar, ou a coleção pode aparentar funcionar em algum caso. Em todos eles, o contrato já foi violado.
Este exemplo pressupõe uma classe já hashável cujo código participa da igualdade e do hash.
# Suponha que codigo participe de __eq__ e de __hash__.
referencia = Referencia("A-10")
estoque = {referencia: 5}
referencia.codigo = "B-20" # altera a identidade após a inserção
# Não se deve confiar no comportamento destas operações:
print(estoque.get(referencia))
print(Referencia("A-10") in estoque)
print(Referencia("B-20") in estoque)Atenção
Guardar o hash calculado antes da mudança não resolve o problema. A igualdade passaria a refletir a nova identidade, enquanto o hash guardado refletiria a antiga. Objetos iguais precisam produzir hashes iguais; preservar um hash antigo quebra essa relação.
Escolha conforme o contrato público da classe:
__hash__ = None. Assim, a classe não pode ser inserida em conjuntos nem usada como chave, evitando um uso inseguro.A terceira opção é útil quando o cadastro precisa mudar, mas uma consulta precisa de uma referência estável.
Uma classe Livro compara apenas isbn e calcula seu hash apenas a partir de isbn. O atributo nota_interna pode ser alterado livremente e não participa dessas operações. Qual política é adequada?
Um CadastroCliente considera dois clientes iguais quando possuem o mesmo email, mas o e-mail pode ser corrigido depois. Qual política de hash você escolheria e por quê?
Escreva pelo menos 40 caracteres (0/40).

Passo 4 de 7
Implemente um hash coerente com a igualdade de valor e mantenha esse contrato estável.
Quando a igualdade já considera dois campos de valor, componha o hash com esses mesmos campos. A forma usual é aplicar hash() a uma tupla: hash((campo1, campo2)).
Na referência de catálogo, codigo e edicao definem o valor. Já observacao é um metadado editável: duas referências podem ser iguais mesmo com observações diferentes. Portanto, ela fica fora de __eq__ e de __hash__.
Os dois campos estáveis seguem juntos para igualdade e hash; o metadado editável permanece fora desse contrato.

Use no hash exatamente os campos que participam da identidade de valor.
O __eq__ abaixo já está alinhado ao domínio. O __hash__ apenas reutiliza a mesma dupla de atributos, sem combinar números de hash manualmente.
class ReferenciaCatalogo:
def __init__(self, codigo, edicao, observacao=""):
self._codigo = codigo
self._edicao = edicao
self.observacao = observacao
def __eq__(self, other):
if not isinstance(other, ReferenciaCatalogo):
return NotImplemented
return (
self._codigo,
self._edicao,
) == (
other._codigo,
other._edicao,
)
def __hash__(self):
return hash((self._codigo, self._edicao))
def editar_observacao(self, nova_observacao):
self.observacao = nova_observacao
primeira = ReferenciaCatalogo("BK-204", 3, "Prateleira A")
equivalente = ReferenciaCatalogo("BK-204", 3, "Em revisão")
print(primeira == equivalente) # True
print(hash(primeira) == hash(equivalente)) # True
indice = {primeira: "disponível"}
print(indice[equivalente]) # disponível
primeira.editar_observacao("Reservado")
print(indice[equivalente]) # disponívelDica
Nesta classe, a operação pública de alteração muda somente observacao. Assim, ela não muda igualdade nem hash. Os campos _codigo e _edicao devem ser tratados como estáveis durante a vida da instância usada como chave ou elemento de conjunto.
Atenção
Os nomes _codigo e _edicao são uma convenção, não uma barreira: ainda seria possível reatribuí-los diretamente. Do mesmo modo, guardar valores em uma tupla não impede reatribuir o atributo que aponta para ela. A segurança vem do contrato de uso da classe: não ofereça operações que alterem seus campos de valor e não os modifique após inserir o objeto em uma coleção hashada.
A tupla externa não resolve componentes não hasháveis. Por exemplo, hash((self._codigo, self.etiquetas)) falha se etiquetas for uma lista, pois listas não são hasháveis. Inclua apenas valores hasháveis que realmente participem da igualdade; não inclua um campo só porque ele existe na instância.
Complete a expressão de retorno:
def __hash__(self):
return ____A igualdade da classe usa self._codigo e self._edicao.

Passo 5 de 7
Use hash para buscas em memória, não como identificador salvo ou número permanente.
O contrato exige que o hash de um objeto hashável permaneça estável durante a vida daquele objeto. Isso permite que dicionários e conjuntos o localizem na execução atual.
Esse contrato não promete que o número retornado por hash() será o mesmo quando você executar o programa novamente. Em especial, hashes de str — e de tuplas que incluem strings — podem mudar entre processos do Python por causa da aleatorização interna.
A relação importante é dentro de uma execução: objetos equivalentes devem ter hashes iguais.

O mesmo valor de domínio pode receber números de hash diferentes em execuções distintas sem quebrar o contrato.
Exemplo
Evite um teste como este:
assert hash(referencia) == 123456
Esse teste pode falhar em outra execução mesmo que __hash__ esteja correto. Prefira verificar relações do contrato, por exemplo:
assert primeira == segundaassert hash(primeira) == hash(segunda)
Também não exija hashes diferentes para objetos diferentes: colisões são permitidas.
Se você precisa gravar uma referência em arquivo para encontrá-la depois, salve os próprios campos identificadores do domínio — por exemplo, codigo e edicao —, não o resultado de hash().
Ao carregar os dados em outra execução, reconstrua ou consulte a referência pelos campos. O hash continua sendo um mecanismo interno de consulta em memória.
Exemplo
Para uma referência cuja igualdade usa codigo e edicao, um JSON pode registrar:
{"codigo": "PY-204", "edicao": 3}Não use algo como:
{"hash": 481516}O primeiro formato conserva o significado da referência. O segundo conserva apenas um número sem garantia de repetição nem de unicidade.
Dica
Use hash(objeto) para apoiar dicionários e conjuntos enquanto o programa está em execução. Para salvar, transmitir ou comparar uma referência ao longo do tempo, use os campos que definem essa referência.
Se hash(referencia) produz valores diferentes em duas execuções do programa, então __hash__ está necessariamente incorreto.

Passo 6 de 7
Execute um script autocontido para verificar o comportamento de objetos equivalentes em dicionários e conjuntos.
Não teste hashes por um número específico: ele pode variar entre execuções. Teste as relações que o contrato exige.
Para duas referências de catálogo equivalentes, verifique:
a == b é verdadeiro e hash(a) == hash(b) também;a é encontrada e atualizada com b, sem criar outra entrada;Em contraste, uma classe de valor mutável declarada com __hash__ = None deve gerar TypeError ao ser usada como chave ou elemento de conjunto.

O hash direciona a busca; a igualdade confirma que a instância equivalente representa a mesma chave.
Dica
O teste hash(a) == hash(b) não prova que a == b: colisões são permitidas. Por isso, o script também testa uma colisão controlada com objetos diferentes.
Crie um arquivo chamado verificar_hash.py, copie o código abaixo e rode no seu computador com python verificar_hash.py. Se tudo estiver coerente, a única saída será a mensagem final.
class ReferenciaCatalogo:
def __init__(self, codigo, edicao, observacao):
self.codigo = codigo
self.edicao = edicao
self.observacao = observacao # metadado editável
def __eq__(self, other):
if not isinstance(other, ReferenciaCatalogo):
return NotImplemented
return (self.codigo, self.edicao) == (other.codigo, other.edicao)
def __hash__(self):
return hash((self.codigo, self.edicao))
class CadastroMutavel:
def __init__(self, email):
self.email = email
def __eq__(self, other):
if not isinstance(other, CadastroMutavel):
return NotImplemented
return self.email == other.email
__hash__ = None
class ColisaoDeTeste:
def __init__(self, valor):
self.valor = valor
def __eq__(self, other):
if not isinstance(other, ColisaoDeTeste):
return NotImplemented
return self.valor == other.valor
def __hash__(self):
return 0 # somente para testar uma colisão controlada
primeira = ReferenciaCatalogo("PY-101", 3, "prateleira A")
equivalente = ReferenciaCatalogo("PY-101", 3, "revisada")
# Objetos distintos, mas mesmo valor para igualdade e hash.
assert primeira is not equivalente
assert primeira == equivalente
assert hash(primeira) == hash(equivalente)
# Uma chave equivalente localiza e atualiza a entrada existente.
estoque = {primeira: 4}
assert estoque[equivalente] == 4
estoque[equivalente] = 7
assert len(estoque) == 1
assert estoque[primeira] == 7
# O conjunto elimina a duplicata equivalente.
referencias = {primeira, equivalente}
assert len(referencias) == 1
# Alterar metadado fora da identidade de valor não altera o contrato.
hash_antes = hash(primeira)
primeira.observacao = "emprestada até sexta"
assert primeira == equivalente
assert hash(primeira) == hash_antes
assert estoque[equivalente] == 7
assert len({primeira, equivalente}) == 1
# Uma classe não hashável é rejeitada em dicionários e conjuntos.
cadastro = CadastroMutavel("ana@exemplo.com")
for operacao in (
lambda: hash(cadastro),
lambda: {cadastro: "ativo"},
lambda: {cadastro},
):
try:
operacao()
except TypeError:
pass
else:
raise AssertionError("CadastroMutavel deveria ser não hashável")
# Colisão não transforma valores diferentes em objetos iguais.
x = ColisaoDeTeste("A")
y = ColisaoDeTeste("B")
assert hash(x) == hash(y)
assert x != y
assert len({x, y}) == 2
assert {x: "primeiro", y: "segundo"}[y] == "segundo"
print("Todas as verificações passaram.")Atenção
ColisaoDeTeste.__hash__ retorna 0 apenas para demonstrar que o dicionário e o conjunto ainda distinguem valores pela igualdade. Não use hash constante como implementação normal: ele não representa os campos de valor e prejudica as consultas.
Execute o script no seu computador. O que a saída e as verificações mostraram sobre: consulta por chave equivalente, remoção de duplicata, mudança de metadado e rejeição de CadastroMutavel?
Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 7
Aplique o contrato de igualdade e hash em dois modelos e valide o comportamento em dicionários e conjuntos.
Uma ReferênciaCatalogo é identificada por codigo e versao, campos que a interface mantém estáveis. Ela pode ser hashável: igualdade e hash usam exatamente esses campos.
Um CadastroContato é identificado por email, mas esse dado pode ser alterado. Como a identidade de valor pode mudar, a decisão segura é declarar __hash__ = None. Um atributo mutável, como uma observação, não impediria o hash por si só; o problema é a mutação de um campo que participa da igualdade.

Campos estáveis de valor podem compor o hash; campos de igualdade que podem mudar exigem uma política não hashável.
O primeiro modelo compõe o hash com uma tupla dos campos relevantes e hasháveis. O segundo torna a recusa explícita. Copie o script para um arquivo, execute-o no seu computador e confira as mensagens exibidas.
class ReferenciaCatalogo:
def __init__(self, codigo, versao, observacao):
self.codigo = codigo
self.versao = versao
self.observacao = observacao
def __eq__(self, outro):
if not isinstance(outro, ReferenciaCatalogo):
return NotImplemented
return (self.codigo, self.versao) == (outro.codigo, outro.versao)
def __hash__(self):
return hash((self.codigo, self.versao))
class CadastroContato:
def __init__(self, email, nome):
self.email = email
self.nome = nome
def __eq__(self, outro):
if not isinstance(outro, CadastroContato):
return NotImplemented
return self.email == outro.email
__hash__ = None
original = ReferenciaCatalogo("LIV-42", 3, "primeira revisão")
equivalente = ReferenciaCatalogo("LIV-42", 3, "texto atualizado")
indice = {original: "disponível"}
print(original == equivalente) # True
print(hash(original) == hash(equivalente)) # True
print(indice[equivalente]) # disponível
print(len({original, equivalente})) # 1
original.observacao = "revisão editorial"
print(indice[equivalente]) # disponível
contato = CadastroContato("ana@exemplo.com", "Ana")
try:
{contato}
except TypeError as erro:
print(type(erro).__name__) # TypeErrorPara impedir o uso de CadastroContato como chave enquanto email pode mudar, complete a linha:
_____ = None
Para cada classe do script, explique a política de hash escolhida e cite duas verificações que demonstram que a decisão está correta.
Escreva pelo menos 80 caracteres (0/80).
Resumo
__hash__ se esses campos permanecerem estáveis enquanto a instância puder estar em dicionários ou conjuntos.hash((campo1, campo2, ...)); cada componente precisa ser hashável.hash() como identificador persistente entre execuções.__hash__ = None.Parabéns! Você concluiu: Manter igualdade e hash coerentes
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks