Objetos hashable em Python

itepifanio

Ítalo Epifânio

Posted on July 24, 2022

Objetos 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 pela linguagem.

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:

from dataclasses import dataclass

@dataclass
class Pessoa:
    nome: str
    cpf: str

p = Pessoa(nome="Ítalo Epifânio", cpf="1010101010")
pessoas = set()
pessoas.add(p)
Enter fullscreen mode Exit fullscreen mode

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:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'Pessoa'
Enter fullscreen mode Exit fullscreen mode

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.

@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)
Enter fullscreen mode Exit fullscreen mode

Agora o código consegue ser executado sem o erro anterior. Se executarmos print(pessoas) veremos que o conjunto contém o seguinte valor:

{Pessoa(nome='Ítalo Epifânio', cpf=1010101010)}
Enter fullscreen mode Exit fullscreen mode

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:

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')}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
itepifanio
Ítalo Epifânio

Posted on July 24, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related