À partir d'une question récente SO (voir Créer un dictionnaire dans python qui est indexé par des listes ) je me suis rendu compte que j'avais probablement eu une erreur conception de la signification des objets lavables et immuables en python.
Hashing est le processus de conversion d'une grande quantité de données en une quantité beaucoup plus petite (généralement un seul entier) d'une manière répétable afin qu'elle puisse être consultée dans une table en temps constant (O(1)
), ce qui est important pour les algorithmes et les structures de données hautes performances.
Immuabilité est l'idée qu'un objet ne changera pas d'une manière importante après sa création, en particulier de quelque manière que ce soit qui pourrait changer la valeur de hachage de cet objet.
Les deux idées sont liées car les objets qui sont utilisés comme clés de hachage doivent généralement être immuables afin que leur valeur de hachage ne change pas. S'il était autorisé à changer, l'emplacement de cet objet dans une structure de données telle qu'une table de hachage changerait, puis l'objectif de hachage pour plus d'efficacité serait vaincu.
Pour vraiment comprendre l'idée, vous devez essayer d'implémenter votre propre table de hachage dans un langage comme C/C++, ou lire la mise en œuvre Java de la classe HashMap
).
- Y a-t-il des objets mutables qui sont lavables ou des objets immuables qui ne sont pas lavables?
En Python, Tuple est immuable, mais il n'est lavable que si tous ses éléments sont lavables.
>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
TypeError: unhashable type: 'list'
Types hashable
Techniquement, hashable signifie que la classe définit __hash__()
. Selon les documents:
__hash__()
devrait retourner un entier. 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.
Je pense que pour les types intégrés Python, tous les types lavables sont également immuables.
Il serait difficile mais peut-être pas impossible d'avoir un objet mutable qui définisse néanmoins __hash__()
.
Du Glossaire Python :
Un objet est hachable s'il a une valeur de hachage qui ne change jamais pendant sa durée de vie (il a besoin d'une méthode
__hash__()
), et peut être comparée à d'autres objets (il a besoin d'une__eq__()
ou__cmp__()
méthode). Les objets hachables qui se comparent égaux doivent avoir la même valeur de hachage.La capacité de hachage rend un objet utilisable comme clé de dictionnaire et membre d'ensemble, car ces structures de données utilisent la valeur de hachage en interne.
Tous les objets intégrés immuables de Python sont lavables, alors qu'aucun conteneur mutable (comme les listes ou les dictionnaires) ne l'est. Les objets qui sont des instances de classes définies par l'utilisateur sont hachables par défaut; ils se comparent tous inégaux, et leur valeur de hachage est leur id ().
Les dictés et les ensembles doivent utiliser un hachage pour une recherche efficace dans une table de hachage; les valeurs de hachage doivent être immuables, car la modification du hachage perturbera les structures de données et entraînera l'échec du dict ou de l'ensemble. La façon la plus simple de rendre la valeur de hachage immuable est de rendre l'objet entier immuable, c'est pourquoi les deux sont souvent mentionnés ensemble.
Bien qu'aucun des objets mutables intégrés ne soit lavable, il est possible de créer un objet mutable avec une valeur de hachage qui est pas mutable. Il est courant qu'une partie seulement de l'objet représente son identité, tandis que le reste de l'objet contient des propriétés qui sont libres de changer. Tant que la valeur de hachage et les fonctions de comparaison sont basées sur l'identité mais pas sur les propriétés modifiables et que l'identité ne change jamais, vous avez satisfait aux exigences.
Il y a un implicite même s'il n'y a pas de relation explicite forcée entre immuable et lavable en raison de l'interaction entre
Il n'y a pas de problème ici sauf si vous redéfinissez __eq__
donc la classe d'objets définit l'équivalence sur la valeur.
Une fois que vous avez fait cela, vous devez trouver une fonction de hachage stable qui renvoie toujours la même valeur pour les objets qui représentent la même valeur (par exemple, où __eq__
) renvoie True et ne change jamais pendant la durée de vie d'un objet.
Il est difficile de voir une application où cela est possible, considérez une classe possible A qui répond à ces exigences. Bien qu'il existe un cas dégénéré évident où __hash__
renvoie une constante.
Maintenant:-
>>> a = A(1)
>>> b = A(1)
>>> c = A(2)
>>> a == b
True
>>> a == c
False
>>> hash(a) == hash(b)
True
>>> a.set_value(c)
>>> a == c
True
>>> assert(hash(a) == hash(c)) # Because a == c => hash(a) == hash(c)
>>> assert(hash(a) == hash(b)) # Because hash(a) and hash(b) have compared equal
before and the result must stay static over the objects lifetime.
En fait, cela signifie à la création hachage (b) == hachage (c), malgré le fait qu'il n'y a jamais de comparaison égale. J'ai du mal à voir quand même pour définir utilement __hash__
() pour un objet mutable qui définit la comparaison par valeur.
Remarque: __lt__
, __le__
, __gt__
et __ge__
les comparaisons ne sont pas affectées, vous pouvez donc toujours définir un ordre des objets hachables, modifiables ou autrement en fonction de leur valeur.
juste parce que c'est le meilleur hit de Google, voici un moyen simple de rendre un objet mutable lavable:
>>> class HashableList(list):
... instancenumber = 0 # class variable
... def __init__(self, initial = []):
... super(HashableList, self).__init__(initial)
... self.hashvalue = HashableList.instancenumber
... HashableList.instancenumber += 1
... def __hash__(self):
... return self.hashvalue
...
>>> l = [1,2,3]
>>> m = HashableList(l)
>>> n = HashableList([1,2,3])
>>> m == n
True
>>> a={m:1, n:2}
>>> a[l] = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> m.hashvalue, n.hashvalue
(0, 1)
En fait, j'ai trouvé une utilisation pour quelque chose comme ça lors de la création d'une classe pour convertir des enregistrements SQLAlchemy en quelque chose de mutable et plus utile pour moi, tout en conservant leur possibilité de les utiliser comme clés dict.
Immuable signifie que l'objet ne changera pas de manière significative au cours de sa durée de vie. C'est une idée vague mais courante dans les langages de programmation.
La capacité de hachage est légèrement différente et renvoie à la comparaison.
hashable Un objet est hashable s'il a une valeur de hachage qui ne change jamais pendant sa durée de vie (il a besoin d'une fonction
__hash__()
) et peut être comparé à d'autres objets (il a besoin d'une méthode__eq__()
ou__cmp__()
). Les objets hachables qui se comparent égaux doivent avoir la même valeur de hachage.
Toutes les classes définies par l'utilisateur ont __hash__
, méthode qui, par défaut, renvoie simplement l'ID d'objet. Un objet qui répond aux critères de possibilité de hachage n'est donc pas nécessairement immuable.
Les objets de toute nouvelle classe que vous déclarez peuvent être utilisés comme clé de dictionnaire, sauf si vous l'empêchez, par exemple, en lançant de __hash__
Nous pourrions dire que tous les objets immuables sont lavables, car si le hachage change pendant la durée de vie de l'objet, cela signifie que l'objet a muté.
Mais pas tout à fait. Considérons un Tuple qui a une liste (modifiable). Certains disent que Tuple est immuable, mais en même temps, il n'est pas du tout lavable (lancers).
d = dict()
d[ (0,0) ] = 1 #perfectly fine
d[ (0,[0]) ] = 1 #throws
L'habilité et l'immuabilité se réfèrent à l'instanciation de l'objet, pas au type. Par exemple, un objet de type Tuple peut être lavable ou non.
Dans Python ils sont principalement interchangeables; puisque le hachage est censé représenter le contenu, il est donc tout aussi modifiable que l'objet, et avoir un objet changeant la valeur de hachage le rendrait inutilisable en tant que clé dict.
Dans d'autres langues, la valeur de hachage est davantage liée à l'identité des objets, et non (nécessairement) à la valeur. Ainsi, pour un objet mutable, le pointeur pourrait être utilisé pour démarrer le hachage. En supposant, bien sûr, qu'un objet ne bouge pas en mémoire (comme le font certains GC). C'est l'approche utilisée à Lua, par exemple. Cela rend un objet mutable utilisable comme clé de table; mais crée plusieurs surprises (désagréables) pour les débutants.
En fin de compte, avoir un type de séquence immuable (tuples) le rend plus agréable pour les "clés à valeurs multiples".
Hashable signifie que la valeur d'une variable peut être représentée (ou, plutôt, encodée) par une constante - chaîne, nombre, etc. Maintenant, quelque chose qui est sujet à changement (mutable) ne peut pas être représenté par quelque chose qui ne l'est pas. Par conséquent, toute variable qui est mutable ne peut pas être lavable et, du même coup, seules les variables immuables peuvent être lavables.
J'espère que cela t'aides ...