Why is an instance of a collections.abc.MutableMapping subclass unhashable by default?
answer
- Equality and hashing travel together
- Defining one without the other disables it
- The base class sets the attribute to None
- Read-only in the interface is not immutable
- Opt back in over a frozenset of items
basics
~20 sBecause the base class defines eq and sets hash to None, so hash() raises TypeError: unhashable type. Anything defining equality without a matching hash is unhashable in Python, and a mapping whose contents change should not be a dict key anyway.
solid answer
~40 s`collections.abc.Mapping` gives you value equality, and Python's rule is that a class defining `__eq__` without `__hash__` becomes unhashable — the base class makes that explicit by setting `__hash__` to `None`. So `hash(m)` raises `TypeError: unhashable type: 'MyMap'`, and instances cannot be dict keys or set members. The reason is correctness: two mappings that compare equal must hash equal, and the contents of a mutable mapping change under you. The surprise is that inheriting the read-only `Mapping` does not help — it is the class carrying `__eq__`, so it is unhashable too. If your mapping really is immutable, define `__hash__` yourself, typically over `frozenset(self.items())`, and only if the values are hashable as well.
code
python · 26 linesfrom collections.abc import Mapping
class FrozenConfig(Mapping):
def __init__(self, **kwargs):
self._store = dict(kwargs)
def __getitem__(self, key): return self._store[key]
def __iter__(self): return iter(self._store)
def __len__(self): return len(self._store)
try:
hash(FrozenConfig(sink="warehouse"))
except TypeError as exc:
print("TypeError:", exc) # unhashable type: 'FrozenConfig'
class HashableConfig(FrozenConfig):
def __hash__(self):
return hash(frozenset(self.items()))
key = HashableConfig(sink="warehouse", rows=3)
print(hash(key) == hash(HashableConfig(rows=3, sink="warehouse")))
print({key: "nightly"}[key])go deeper
Recognise the message: unhashable type naming your own mapping class means the object was used as a dictionary key or put in a set. Remember that defining equality is what took hashing away.
Explain the pairing rule — equal objects must hash equal, so a class defining eq loses hash unless it defines one — and why the read-only base class is unhashable too, since equality lives there.
Judge whether restoring hashing is honest. Writing hash over a frozenset of the items asserts an immutability nothing enforces, so say how the class guarantees it and what happens when a value is itself unhashable.
Decide whether a hashable mapping type belongs in the codebase at all, given that its safety rests on a convention rather than a check, and what you would use instead where a stable, comparable configuration key is genuinely needed.
## The rule underneath Python requires that objects comparing equal hash equal, so the language enforces the pairing structurally: define `__eq__` in a class without also defining `__hash__` and the class becomes unhashable. `collections.abc.Mapping` defines `__eq__` — it is one of the mixins you inherit — and it makes the consequence explicit by setting `__hash__` to `None`. `MutableMapping` inherits both. The result surfaces as: ``` TypeError: unhashable type: 'CaseInsensitiveDict' ``` the first time someone uses your mapping as a dictionary key, puts it in a set, or passes it to a function that memoises on its arguments. ## Why that is the right default A hash is a promise about a value that must not change while the object sits in a hash table. A mutable mapping breaks the promise by definition: store one as a key, mutate it, and it is lost in the bucket it no longer belongs to. `dict` itself is unhashable for exactly this reason, and the abstract base class simply keeps the same rule for anything wearing the same interface. ## The part that surprises people Inheriting the read-only `collections.abc.Mapping` instead does **not** make instances hashable. Equality lives on `Mapping`, so `__hash__` is `None` there too. Read-only in the interface sense means callers cannot write through your object — it says nothing about whether the data underneath can change, which is precisely why the base class refuses to guess. If your mapping genuinely is immutable, you opt in by writing `__hash__` yourself. The usual implementation hashes a `frozenset` of the items, which is order-insensitive and therefore agrees with the inherited `__eq__`. It only works when the values are hashable too — a configuration mapping whose values are lists will raise from inside your own `__hash__`. ## A sibling of the same shape `Mapping` also sets `__reversed__` to `None`, so `reversed(m)` raises `TypeError: 'MyMap' object is not reversible` even when the store underneath is a `dict` — and `dict` has been reversible since 3.8. Same mechanism, same fix: define `__reversed__` yourself, delegating to the backing store, if reverse iteration is meaningful for your mapping.
- How would you make a read-only mapping usable as a dictionary key?Define `__hash__` on your class, normally as `hash(frozenset(self.items()))` so it is order-insensitive and agrees with the inherited equality. You are asserting immutability the language cannot verify, so the backing store must never be mutated after construction, and every value must itself be hashable or the hash raises.
- Why does reversed() fail on a Mapping subclass that wraps a dict?`collections.abc.Mapping` sets `__reversed__` to `None`, which makes the object explicitly non-reversible regardless of what it stores. `dict` gained reverse iteration in 3.8, but that never travels through the abstract base class. Define `__reversed__` on your class, delegating to `reversed(self._store)`, when reverse iteration is meaningful.
- Is an unhashable mapping the same problem as an unhashable key?No, and mixing them up sends people to the wrong line. `TypeError: unhashable type` for your mapping class means the mapping itself was used as a key or set member. The same message naming `list` or `dict` means a key you tried to store inside a mapping is not hashable. Read the type in the message before changing anything.
saying these in an interview costs you the question
- Thinks the unhashability is inherited from the wrapped dict
- Expects a read-only Mapping subclass to be hashable
- Adds __hash__ to a mapping whose contents still change
- Confuses an unhashable key with an unhashable mapping
- Treats __eq__ and __hash__ as unrelated methods
- Assumes reversed() works because the backing store is a dict