Supposons que j'ai une namedtuple
comme ceci:
EdgeBase = namedtuple("EdgeBase", "left, right")
Je souhaite implémenter une fonction de hachage personnalisée pour cela, je crée donc la sous-classe suivante:
class Edge(EdgeBase):
def __hash__(self):
return hash(self.left) * hash(self.right)
Puisque l'objet est immuable, je veux que la valeur de hachage ne soit calculée qu'une seule fois. Je le fais donc:
class Edge(EdgeBase):
def __init__(self, left, right):
self._hash = hash(self.left) * hash(self.right)
def __hash__(self):
return self._hash
Cela semble fonctionner, mais je ne suis vraiment pas sûr de sous-classer et d'initialiser en Python, en particulier avec les n-uplets. Y a-t-il des pièges à cette solution? Existe-t-il un moyen recommandé de procéder? Est-ce que ça va? Merci d'avance.
éditer pour 2017: s'avère que namedtuple
n'est pas une bonne idée . attrs est l'alternative moderne.
class Edge(EdgeBase):
def __new__(cls, left, right):
self = super(Edge, cls).__new__(cls, left, right)
self._hash = hash(self.left) * hash(self.right)
return self
def __hash__(self):
return self._hash
__new__
est ce que vous voulez appeler ici car les n-uplets sont immuables. Les objets immuables sont créés dans __new__
puis renvoyés à l'utilisateur au lieu d'être renseignés avec des données dans __init__
.
cls
doit être passé deux fois à l'appel super
de __new__
car __new__
est, pour des raisons historiques/impaires, implicitement une staticmethod
.
Le code de la question pourrait bénéficier d'un super appel dans le __init__
au cas où il serait un jour sous-classé dans une situation d'héritage multiple, mais sinon, est correct.
class Edge(EdgeBase):
def __init__(self, left, right):
super(Edge, self).__init__(left, right)
self._hash = hash(self.left) * hash(self.right)
def __hash__(self):
return self._hash
Alors que les tuples sont en lecture seule, seules les parties Tuple de leurs sous-classes sont en lecture seule, d'autres propriétés peuvent être écrites comme d'habitude, ce qui permet l'affectation à _hash, que ce soit en __init__
ou __new__
. Vous pouvez rendre la sous-classe entièrement en lecture seule en définissant __slots__
sur (), ce qui présente l’avantage supplémentaire d’économiser de la mémoire, mais vous ne pourriez alors pas affecter à _hash.
Dans Python 3.7+, vous pouvez maintenant utiliser dataclasses pour créer facilement des classes pouvant être utilisées.
Code
En supposant que les types int
de left
et right
, nous utilisons le hachage par défaut via unsafe_hash
+ mot-clé:
import dataclasses as dc
@dc.dataclass(unsafe_hash=True)
class Edge:
left: int
right: int
hash(Edge(1, 2))
# 3713081631934410656
Maintenant, nous pouvons utiliser ces objets (modifiables) hachables comme éléments d’un ensemble ou (clés d’un dict).
{Edge(1, 2), Edge(1, 2), Edge(2, 1), Edge(2, 3)}
# {Edge(left=1, right=2), Edge(left=2, right=1), Edge(left=2, right=3)}
Détails
Nous pouvons également remplacer la fonction __hash__
:
@dc.dataclass
class Edge:
left: int
right: int
def __post_init__(self):
# Add custom hashing function here
self._hash = hash((self.left, self.right)) # emulates default
def __hash__(self):
return self._hash
hash(Edge(1, 2))
# 3713081631934410656
En développant le commentaire de @ ShadowRanger, la fonction de hachage personnalisée de l'OP n'est pas fiable. En particulier, les valeurs d'attribut peuvent être échangées, par ex. hash(Edge(1, 2)) == hash(Edge(2, 1))
, ce qui n'est probablement pas ce qui est prévu.
+Notez que le nom "unsafe" suggère que le hachage par défaut sera utilisé malgré la mutabilité de l'objet. Cela peut être indésirable, en particulier lorsque vous attendez des clés immuables. Le hachage immuable peut être activé avec les mots-clés appropriés. Voir aussi plus d'informations sur logique de hachage dans les classes de données et un problème lié .