Python Hash: How hash() and __hash__ Work
python **hash**: Understand Python's hash() built-in and __hash__ method: the hash/equality contract, hashable types, and when to implement custom hashing.
In Python, the hash() built-in returns an integer for any hashable object, and that integer drives the internal behavior of dictionaries and sets. Understanding how python **hash** works — the __hash__ method, the equality contract, and the runtime cost of collisions — matters whenever you store custom objects in a set or use them as dictionary keys.
What the hash() Built-In Returns
hash() accepts any hashable object and returns an integer. For built-in types, the value is derived from the object's content rather than its identity:
hash("hello") hash(42) hash((1, 2, 3))
The exact integer values are not stable across separate Python processes. Strings and bytes are hashed with a random seed by default, so the same string produces different hashes in different runs. Within a single process, however, the same object always produces the same hash, which is the property that dictionaries and sets depend on.
Hashability and the hash Method
An object is hashable when it defines __hash__ and __eq__, either explicitly or through inheritance. Immutable built-ins such as str, int, float, tuple, and frozenset are hashable. Mutable containers such as list, dict, and set are not.
The reason is structural. When an object is stored as a dictionary key or set member, its hash determines which bucket it lands in. If the object's content changes after insertion, its hash changes, and a later lookup computes a different bucket and never finds the entry. Immutability guarantees that the hash remains stable for the object's lifetime.
The eq and hash Contract
Python enforces a logical contract: if two objects compare equal, they must produce the same hash. Violating this breaks dictionary and set lookups, because the hash narrows the search to a bucket and equality then confirms the match.
class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y) def __hash__(self): return hash((self.x, self.y))
The hash is computed from the same fields used in equality, so equal points always hash identically. This is the standard pattern for custom hashable classes.
What Happens When You Define Only eq
Defining __eq__ without __hash__ makes instances unhashable. Python sets __hash__ to None in that case, which prevents the class from being used in sets or as dictionary keys:
class Tag: def __init__(self, name): self.name = name def __eq__(self, other): return isinstance(other, Tag) and self.name == other.name hash(Tag("a")) # TypeError: unhashable type: 'Tag'
This behavior is intentional. A class that defines equality without a matching hash risks violating the contract above, so Python refuses to guess. If you need both, implement __hash__ explicitly.
How Dict and Set Lookup Uses the Hash
When you insert a key into a dictionary, Python calls hash(key) to select a bucket. On lookup, it calls hash(key) again, finds the same bucket, and then calls __eq__ only on the keys stored in that bucket. The hash performs the coarse search; equality performs the final confirmation.
This two-stage design is why the hash must be consistent with equality. Two keys that compare equal but hash differently would land in different buckets, and a lookup for one would never encounter the other.
Hash Collisions and Their Runtime Cost
A collision happens when two distinct keys produce the same hash. Python resolves collisions by probing within the hash table. With a well-distributed hash, lookups remain close to O(1). When many keys collide, the affected bucket grows, and lookups degrade toward linear scans.
For user-defined classes, a poor __hash__ can force this degradation. Returning a constant from __hash__ places every instance in the same bucket, turning dictionary and set operations into O(n) comparisons. The tuple-based pattern shown earlier distributes values well for most real-world data.
Hash Randomization and Security
Python randomizes string and bytes hashes by default to reduce the risk of denial-of-service attacks that exploit predictable collisions in web-facing code. The seed is set at interpreter startup and can be controlled with the PYTHONHASHSEED environment variable. Setting it to 0 disables randomization, which is occasionally useful for debugging but should not be the default in production.
One CPython-specific detail: the hash of an integer is the integer itself, except that -1 is mapped to -2 because -1 is reserved as an error sentinel in the C API. This behavior is an implementation detail, not part of the language specification.
When to Implement Custom hash
Implement __hash__ whenever you define __eq__ and instances must work as dictionary keys or set members. The reliable pattern is to hash a tuple of the fields used in equality:
def __hash__(self): return hash((self.x, self.y))
Fields that can change after the object is created should not participate in the hash. If a mutable field is part of the hash and the object is already stored in a set, mutating that field makes the object unfindable. The same constraint applies to equality: changing a field used by __eq__ can make two previously equal objects unequal while they are still stored in a collection. For this reason, hashable classes should treat their hash-relevant fields as effectively immutable.