Si j'ai un tuple avec de nombreux éléments, son hachage est-il calculé à partir des IDs de ses éléments ou du contenu de ses éléments?
Aucun des deux. Il est calculé sur la base des hachages de ces éléments, pas sur leur "contenu" (valeurs/attributs), ni sur leurs IDs.
Les bases : pourquoi les hachages sont utilisés de cette manière
Jetez un œil à ce paragraphe dans la glossaire de documentation de Python.
Que quelque chose soit hashable ou non, et comment il est hashé, dépend de l'implémentation de sa méthode __hash__()
. En soi, Python n'a aucune idée de la mutabilité d'un objet.
Un hachage est utile pour l'identification des objets. Par exemple, il accélère la récupération des données à partir d'un dict
, identifiant la valeur arbitraire d'une clé par une seule valeur numérique dans un intervalle fini - le hachage de la clé.
Un hachage devrait rester inchangé tout au long de la durée de vie de l'objet. Sinon, un objet pourrait être associé à deux valeurs différentes dans un dict
, ou être inclus dans un set
deux fois, dès que son hachage change.
Il ne suffit pas de comparer deux objets par leurs hachages : à la fin de la journée, vous pourriez quand même avoir besoin d'effectuer des vérifications d'égalité, car il peut y avoir une collision entre les hachages de différents objets. C'est pourquoi les objets hashables doivent avoir __eq__()
implémenté.
Cela ramène à la mutabilité : si un objet hashable mute de manière à modifier les comparaisons d'égalité avec des objets hashables, en particulier ceux avec le même hachage - il rompt le contrat, et pourrait entraîner les mêmes bizarreries qu'un hachage muté. Les objets hashables ne doivent pas modifier les comparaisons entre eux.
Des objets hashables qui sont égaux les uns aux autres devraient avoir le même hachage. Cet accord général rend tout le reste plus simple - il est naturel de supposer que x == y
implique que à la fois x
et y
se mappent à la même valeur dans un dict
.
Hachage d'un tuple
Prenons votre premier exemple. Le tuple
se hache lui-même sur la base de ses éléments, alors que son deuxième élément, la list
, n'a pas de hachage du tout - la méthode __hash__
n'est pas implémentée pour elle. Ainsi, la méthode tuple.__hash__
échoue.
C'est pourquoi un tuple
avec un objet list
à l'intérieur n'est pas hashable. Comme vous le voyez, il est donc également incorrect de dire qu'un hachage de tuple
est basé sur les IDs de ses éléments.
Remarquez que si la list
était hashable ici, et que le hachage était basé sur ses éléments, les changer modifierait le hachage du tuple
extérieur, rompant le contrat.
Pourquoi ma classe personnalisée ne nécessite pas de __hash__()
?
Jetons un œil à la documentation du modèle de données Python, et ce qu'elle a à dire sur le sujet :
Les classes définies par l'utilisateur ont par défaut les méthodes __eq__()
et __hash__()
; avec elles, tous les objets se comparent de manière différente (sauf avec eux-mêmes) et x.__hash__()
renvoie une valeur appropriée telle que x == y
implique à la fois que x est y
et que hash(x) == hash(y)
.
En somme, l'implémentation par défaut compare l'identité des objets, qui n'a rien à voir avec les attributs de l'objet. C'est pourquoi vous pouvez changer les valeurs "à l'intérieur" de l'objet de votre classe personnalisée sans modifier son hachage.
C'est aussi pourquoi vous n'avez pas à définir __hash__()
pour vos classes - Python le fait pour vous dans ce cas.
À cet égard, vous avez raison - l'implémentation par défaut (de CPython) de la fonction de hachage pour les classes personnalisées repose sur le id()
d'un objet (et non sur les valeurs "à l'intérieur" de celui-ci). Il s'agit d'un détail d'implémentation, et cela diffère selon les versions de Python.
Dans les versions récentes de Python, la relation entre hash()
et id()
implique de la randomisation. Cela empêche certains types d'attaques par déni de service, où la création de collisions de hachage arbitraires pourrait ralentir considérablement les applications web. Voir PEP-456.
Comment se hache-t-il en réalité ?
Alors que les détails sont assez compliqués et impliquent probablement des mathématiques avancées, l'implémentation de la fonction de hachage pour les objets tuple est écrite en C, et peut être consultée ici (voir static Py_hash_t tuplehash(PyTupleObject *v)
).
Le calcul implique un XOR d'une constante avec les hachages de chacun des éléments du tuple. La ligne responsable du hachage des éléments est la suivante :
y = PyObject_Hash(*p++);
Donc, pour répondre à votre question initiale : il réalise une sorte de tour de magie XOR avec les hachages de chacun de ses éléments. Le fait que les contenus et les attributs de ces éléments soient pris en compte ou non dépend de leurs fonctions de hachage spécifiques.
0 votes
La réponse courte est que
hash(tb) == hash(tc)
parce que bien queid(a)
etid(b)
ne soient pas égaux,hash(a)
ethash(b)
sont les mêmes.