Objeto hashable em Python
Hash de objetos é uma representação númerica inteira que é obtida utilizando o dunder method __hash__
. Compreender esse conceito ajuda a entender como as estruturas de dados Python funcionam, uma vez que o hash dos objetos são utilizados internamente.
Programadores Python costumam se deparar com o conceito de hash de objetos quando tentam armazenar um objeto sem hash em uma estrutura de dados da linguagem. Por exemplo:
1
2
3
4
5
6
7
8
9
10
from dataclasses import dataclass
@dataclass
class Pessoa:
nome: str
cpf: str
p = Pessoa(nome="Ítalo Epifânio", cpf="1010101010")
pessoas = set()
pessoas.add(p)
O código acima define uma classe pessoa e um objeto p
do tipo pessoa. Ao tentar adicionar uma pessoa ao conjunto pessoas
o seguinte erro é lançado:
1
2
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'Pessoa'
Isso acontece porque a estrutura de dados “conjunto” do Python utiliza o hash em suas tabelas internas para encontrar o valor do objeto rapidamente. Como nosso objeto não tem hash, o erro acima é lançado.
Para adicionar um hash a um objeto implementa-se as funções __hash__
e __eq__
simultaneamente (caso esteja utilizando Python 2 a função __ne__
também deve ser adicionada). No exemplo a seguir modificamos nossa classe anterior para adquirir essa propriedade.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@dataclass
class Pessoa:
nome: str
cpf: str
def __hash__(self):
return hash(self.cpf)
def __eq__(self, other):
mesma_classe = self.__class__ == other.__class__
mesmo_cpf = self.cpf == other.cpf
return mesma_classe and mesmo_cpf
p = Pessoa(nome="Ítalo Epifânio", cpf="101.010.101-01")
pessoas = set()
pessoas.add(p)
Agora o código consegue ser executado sem o erro anterior. Se executarmos print(pessoas)
veremos que o conjunto contém o seguinte valor:
1
{Pessoa(nome='Ítalo Epifânio', cpf=1010101010)}
Um objeto é dito hashable se o valor de hash nunca é modificado durante sua fase de vida – Python Docs
Apenas implementar os dunder métodos acima não garante que o objeto é hashable. É preciso garantir o valor de hash desse objeto jamais sejá alterado pois isso pode levar a comportamentos não esperados, por exemplo:
1
2
3
4
5
6
7
p = Pessoa(nome="Ítalo Epifânio", cpf="101.010.101-01")
pessoas = set()
pessoas.add(p)
print(p in pessoas) # retorna True
p.cpf = "999.999.999-99"
print(p in pessoas) # retorna False
print(pessoas) # retorna {Pessoa(nome='Ítalo Epifânio', cpf='999.999.999-99')}
Ao alterar o cpf do objeto p
note que o objeto não é mais encontrado na estrutura de dados conjunto (a segunda chamada do print(p in pessoas)
retorna falso) e quando lista-se os valores do conjunto pessoas
nota-se que há ainda um valor lá.
Em resumo: você não pode basear hash de objetos em valores mutáveis. Se o atributo de um objeto pode ser modificado durante seu ciclo de vida comportamentos inesperados podem acontecer.