Vol. 3 — № 09
The Python Loop · KiosqueNewsstand
Blog  
Un atelier Python · Édition d'ApprentissageA Python Workshop · Learning Edition

Le contrat __hash__ / __eq__ : l'objet mutable qu'on ne doit jamais hacher The __hash__ / __eq__ pact: the mutable object you must never hash

Un dict cherche une clé en deux temps : hash() choisit le compartiment, == confirme l'identité dedans. Casse l'accord entre les deux — deux objets égaux avec des hash différents — et le dict cherche au bon endroit un objet qui n'y est jamais rangé. C'est pour ça qu'une liste est unhashable : elle mute, et un objet qui mute après avoir été haché se perd dans son propre compartiment. A dict finds a key in two steps: hash() picks the bucket, == confirms identity inside it. Break the deal between the two — two equal objects with different hashes — and the dict searches the right spot for an object that was never filed there. That's why a list is unhashable: it mutates, and an object that mutates after being hashed loses itself in its own bucket.

AudienceAudience
Dev qui a défini __eq__ sur une classe et s'est heurté à un TypeError, ou pire, à un bug silencieux Dev who defined __eq__ on a class and hit a TypeError — or worse, a silent bug
Format
Self-paced
ChapitresChapters
5
Date
Juil 2026 Jul 2026
≈ 19 min ●●●○ Data modelHashDict & Set

Chapitre 1 en accès libre — la suite (ch. 2 à 5) est réservée. Chapter 1 free to read — the rest (ch. 2–5) is members-only.

01CadrageFraming3 min

Un dict ne cherche pas une clé — il la localise, en deux temps.A dict doesn't search for a key — it locates it, in two steps.

Le numéro précédent a posé le couple __eq__/__hash__ comme un contrat couplé, sans dire pourquoi le langage y tient tant. Voici la raison : un dict ou un set ne compare pas la clé cherchée à toutes les clés existantes, un par un — ce serait aussi lent qu'une liste. Il appelle hash(clé) pour obtenir un entier, s'en sert pour sauter directement à un compartiment interne, et ne compare avec == qu'à l'intérieur de ce compartiment, pour départager d'éventuelles collisions. La rapidité légendaire du dict repose entièrement sur ce raccourci — et le raccourci suppose une règle stricte.The previous issue set up the __eq__/__hash__ pair as a coupled contract, without saying why the language cares so much. Here's why: a dict or set doesn't compare a sought key against every existing key, one by one — that would be as slow as a list. It calls hash(key) to get an integer, uses it to jump straight to an internal bucket, and only compares with == inside that bucket, to break ties on collisions. The dict's legendary speed rests entirely on that shortcut — and the shortcut assumes a strict rule.

hash() choisit le compartiment, == confirme dedanshash() picks the bucket, == confirms inside it
vus = {"alice", "bob", "carol"}
"bob" in vus
# 1. hash("bob")  -> un entier          -> choisit un compartiment
# 2. dans ce compartiment, Python compare "bob" == chaque clé qui s'y trouve
# 3. une égalité confirme -> True. Sans elle -> False, même compartiment ou pas.

# Un set ne parcourt PAS ses éléments un par un : il saute directement
# au compartiment désigné par le hash, puis ne compare qu'à l'intérieur.
Le sous-réflexe du numéroThis issue's sub-reflex

Avant de mettre un objet dans un set ou comme clé de dict, demande : son hash restera-t-il collé à son égalité, pour toute sa vie dans cette structure ? Si la réponse dépend d'un état qui peut changer, l'objet n'a rien à faire là.Before putting an object in a set or as a dict key, ask: will its hash stay glued to its equality, for its whole life inside that structure? If the answer depends on state that can change, the object has no business being there.

Le hash n'est pas une signature.The hash isn't a signature.

hash() ne prouve rien à lui seul : deux objets différents peuvent partager un hash (collision, normale et gérée). C'est == qui tranche. Le hash ne sert qu'à réduire la recherche à une poignée de candidats — sa seule obligation est de ne jamais séparer deux objets égaux dans deux compartiments différents.hash() alone proves nothing: two different objects can share a hash (a collision, normal and handled). == is what settles it. The hash only narrows the search to a handful of candidates — its one obligation is to never split two equal objects into different buckets.

🔒

La suite est réservée The rest is members-only

Le premier numéro est libre. Débloque tout The Python Loop — tous les volumes, à vie — pour 5 €, paiement unique. The first issue is free. Unlock all of The Python Loop — every volume, forever — for €5, one-time.

Retour au kiosqueBack to newsstand