web-dev-qa-db-fra.com

Comment implémenter une bonne fonction __hash__ dans python

Lors de l'implémentation d'une classe avec plusieurs propriétés (comme dans l'exemple de jouet ci-dessous), quelle est la meilleure façon de gérer le hachage?

Je suppose que le __eq__ et __hash__ devrait être cohérent, mais comment implémenter une fonction de hachage appropriée capable de gérer toutes les propriétés?

class AClass:
  def __init__(self):
      self.a = None
      self.b = None

  def __eq__(self, other):
      return other and self.a == other.a and self.b == other.b

  def __ne__(self, other):
    return not self.__eq__(other)

  def __hash__(self):
      return hash((self.a, self.b))

J'ai lu sur cette question que les tuples sont lavables, donc je me demandais si quelque chose comme l'exemple ci-dessus était sensé. C'est ça?

91
abahgat

__hash__ devrait renvoyer la même valeur pour les objets qui sont égaux. Il ne doit pas non plus changer au cours de la durée de vie de l'objet; généralement, vous ne l'implémentez que pour des objets immuables.

Une implémentation triviale serait de simplement return 0. Ceci est toujours correct, mais fonctionne mal.

Votre solution, renvoyant le hachage d'un Tuple de propriétés, est bonne. Mais notez que vous n'avez pas besoin de répertorier toutes les propriétés que vous comparez dans __eq__ dans le Tuple. Si une propriété a généralement la même valeur pour les objets inégaux, laissez-la simplement de côté. Ne faites pas le calcul de hachage plus cher qu'il ne devrait l'être.

Edit: je déconseille d'utiliser xor pour mélanger les hachages en général. Lorsque deux propriétés différentes ont la même valeur, elles auront le même hachage, et avec xor, elles s'annuleront. Les tuples utilisent un calcul plus complexe pour mélanger les hachages, voir tuplehash dans tupleobject.c .

69
adw

Documentation pour object.__hash__(self)

La seule propriété requise est que les objets qui se comparent égaux ont la même valeur de hachage; il est conseillé de mélanger d'une manière ou d'une autre (par exemple en utilisant exclusif ou) les valeurs de hachage pour les composants de l'objet qui jouent également un rôle dans la comparaison des objets.

def __hash__(self):
    return hash(self.a) ^ hash(self.b)
13
S.Lott

C'est dangereux d'écrire

def __eq__(self, other):
  return other and self.a == other.a and self.b == other.b

car si votre objet rhs (c'est-à-dire other) est évalué comme booléen False, il ne sera jamais comparé comme égal à quoi que ce soit!

En outre, vous souhaiterez peut-être vérifier si other appartient à la classe ou à la sous-classe de AClass. Si ce n'est pas le cas, vous obtiendrez une exception AttributeError ou un faux positif (si l'autre classe a les mêmes attributs avec des valeurs correspondantes). Je recommanderais donc de réécrire __eq__ comme:

def __eq__(self, other):
  return isinstance(other, self.__class__) and self.a == other.a and self.b == other.b

Si, par hasard, vous voulez une comparaison inhabituellement flexible, qui compare entre les classes non liées tant que les attributs correspondent par leur nom, vous voudrez toujours éviter au moins AttributeError et vérifier que other ne fonctionne pas ' t avoir des attributs supplémentaires. La façon dont vous le faites dépend de la situation (car il n'existe aucun moyen standard de trouver tous les attributs d'un objet).

12
max