What must you implement to subclass collections.abc.MutableMapping, and what comes free?
answer
- An interface that pays you back
- Read, write, delete, iterate, size
- Five abstract methods, the rest inherited
- The base class brings no storage
- Iteration must yield keys
basics
~10 sFive methods: getitem, setitem, delitem, iter and len. From those five the base class derives get, membership, keys, items, values, pop, popitem, clear, update, setdefault and equality, so you write the storage once.
solid answer
~40 s`collections.abc.MutableMapping` declares five abstract methods — `__getitem__`, `__setitem__`, `__delitem__`, `__iter__` and `__len__` — and the class cannot be instantiated until all five exist. In exchange, `Mapping` contributes `get`, `__contains__`, `keys`, `items`, `values` and `__eq__`, and `MutableMapping` adds `pop`, `popitem`, `clear`, `update` and `setdefault`; every one of them is written in terms of those five. The base class carries no storage of its own — empty `__slots__`, no `__init__` — so you create the backing store in your own `__init__` and usually fill it by calling the inherited `update`. `__iter__` must yield keys, not pairs, because the mixins index back into the mapping with whatever it yields. The payoff is that a small class satisfies every caller that expects a mapping.
code
python · 32 linesfrom collections.abc import MutableMapping
class CaseInsensitiveDict(MutableMapping):
"""A mapping whose string keys compare case-insensitively."""
def __init__(self, data=()):
self._store = {}
self.update(data) # inherited: one __setitem__ per pair
def __getitem__(self, key):
return self._store[key.lower()]
def __setitem__(self, key, value):
self._store[key.lower()] = value
def __delitem__(self, key):
del self._store[key.lower()]
def __iter__(self):
return iter(self._store) # must yield keys
def __len__(self):
return len(self._store)
headers = CaseInsensitiveDict({"Content-Type": "text/csv"})
print(headers["CONTENT-TYPE"]) # text/csv
print(headers.get("missing", "n/a")) # free from Mapping
print("content-type" in headers) # free from Mapping
headers.setdefault("Rows", 0) # free from MutableMapping
print(dict(headers))go deeper
Be ready to name what a mapping must be able to do at all: look a key up, store one, delete one, be iterated, and report its size. Knowing that a base class can hand you the rest is enough at this level.
You are expected to name the five abstract methods without hesitation, list several of the mixins you get back, and explain that the base class holds no storage so your own init creates it and calls the inherited update.
Show that you know how the mixins are implemented, not just that they exist: membership catching KeyError from getitem, update assigning key by key, popitem taking the first key from the iterator. That is what tells you which ones to override.
Own the choice of interface: read-only Mapping versus MutableMapping is an API promise your callers will code against, and a base class that guarantees completeness while costing per-call Python dispatch is a tradeoff you should be able to defend in a design review.
## The bargain `collections.abc.MutableMapping` is an abstract base class that describes the dict-like interface as a small set of required operations plus everything derivable from them. You supply the irreducible core; it supplies the convenience surface. Import it from `collections.abc` — the old aliases directly on `collections` (`collections.Mapping` and friends) were deprecated in 3.3 and **removed in 3.10**, so `from collections import MutableMapping` is an `ImportError` on any supported version. ## The five you must write * `__getitem__(self, key)` — return the value, raise `KeyError` when the key is absent. Raising the right exception is load-bearing: several mixins are built on catching it. * `__setitem__(self, key, value)` — store or replace. * `__delitem__(self, key)` — remove, raising `KeyError` when absent. * `__iter__(self)` — yield **keys**. * `__len__(self)` — return the number of keys. Until all five exist the class is abstract, and instantiating it fails immediately rather than at first use: ``` TypeError: Can't instantiate abstract class Broken without an implementation for abstract method '__iter__' ``` The split across the hierarchy matters when you are choosing a base. `collections.abc.Mapping` — the read-only half — requires only `__getitem__`, `__iter__` and `__len__`; `MutableMapping` adds `__setitem__` and `__delitem__`. If your object genuinely cannot be written to, inherit `Mapping` and callers get a compile-free, honest signal. ## What the mixins give back From `Mapping`: `get`, `__contains__`, `keys`, `items`, `values`, `__eq__` (and the inverse comparison). From `MutableMapping`: `pop`, `popitem`, `clear`, `update`, `setdefault`. They are ordinary Python methods expressed through your five. Their actual shapes are worth knowing because they explain both the power and the cost: * `__contains__` does `try: self[key] except KeyError: return False`. * `get` is the same shape, returning the default instead of `False`. * `update(other)` iterates the other mapping's keys and performs one `self[key] = value` per pair — there is no bulk path. * `popitem` takes `next(iter(self))`, reads it, deletes it, returns the pair; `clear` calls `popitem` in a loop until `KeyError`. * `setdefault` reads, and on `KeyError` writes the default. * `__eq__` returns `NotImplemented` for a non-mapping, otherwise builds a plain `dict` from both sides' items and compares those. ## It brings no storage A common surprise: the base class defines empty `__slots__` and no `__init__`, so inheriting from it allocates nothing. You own the backing store. The idiomatic constructor creates it and then delegates population to the inherited `update`, which routes every pair back through your `__setitem__` — so any normalisation you implement there (case folding, key validation, write-through to a file) applies to construction too, for free. ## The `__iter__` contract `__iter__` must yield keys. It is the single most common implementation bug in a custom mapping, and it fails far from the mistake: `items()` and `values()` iterate keys and index back with them, `update` from another mapping iterates its keys, `popitem` deletes whatever `next(iter(self))` produced, and `dict(m)` builds from the same assumption. Yield 2-tuples and you get tuples used as keys and `KeyError`s that name objects you never stored. ## What else arrives with the base class `keys()`, `items()` and `values()` return the generic view objects `KeysView`, `ItemsView` and `ValuesView`, each holding a reference to your mapping, so a view stays live as the mapping changes. `__eq__` compares equal to any registered mapping, which includes `dict`, so `my_map == {"a": 1}` works without extra effort. Two attributes are deliberately set to `None`: `__hash__`, which makes instances unhashable, and `__reversed__`, which makes `reversed(m)` a `TypeError` even when the store underneath is a `dict`. ## The views are set-like `keys()` and `items()` do more than iteration and membership: the view classes they return are themselves set-like, so `m.keys() & {"region", "rows"}`, `m.keys() - other.keys()` and the symmetric-difference operator all work on your class and return plain sets, for free. `values()` deliberately is not set-like, because values need not be unique or hashable, so it supports only iteration, membership and length. That contrast is a fair summary of what the base class is really selling: behaviour a caller would otherwise hand-build for every mapping-shaped type in the codebase, obtained by writing five methods that describe your storage and nothing else. ## When to reach for it Use `MutableMapping` when the storage is not just a `dict` — a case-folding header table, a view over a file or a remote store, a mapping that logs every write — and you want the full dict-shaped surface without hand-writing a dozen methods. What it promises is interface completeness, not speed: every inherited method is a Python-level call routed through your five, so anything on a hot path should be overridden to delegate straight to the backing store.
- What must `__iter__` yield in a mapping implementation, and what breaks if it yields pairs?It must yield keys. `items()` and `values()` iterate keys and index back into the mapping with them, `update` from another mapping iterates its keys, and `popitem` deletes whatever `next(iter(self))` produced. Yield 2-tuples and every one of those uses a tuple as a key, so you get `KeyError`s naming objects you never stored, and `dict(m)` comes out wrong.
- Why does inheriting from `collections.abc.MutableMapping` not give you a working `__init__`?The base class holds no data — it declares empty `__slots__` and defines no `__init__`, because it cannot know how you store anything. You write `__init__`, create the backing store, and then call the inherited `update` to populate it, which routes every pair through your own `__setitem__` so construction and later writes share one code path.
- What does the inherited `__eq__` compare, and what does it cost?It returns `NotImplemented` when the other object is not a mapping, so Python falls back to identity comparison. Otherwise it builds a plain `dict` from each side's items and compares those, which allocates two dictionaries per comparison and is O(n) in time and memory. Comparing against a literal `dict` works because `dict` is registered as a mapping.
It is a kit car: you bolt in the engine and wheels, and the chassis already has the dashboard, mirrors and horn wired to whatever you bolted in.
saying these in an interview costs you the question
- Says you have to hand-write every dict method yourself
- Thinks the base class allocates the backing storage for you
- Has __iter__ yield key/value pairs instead of keys
- Believes inheriting the base class makes the class as fast as dict
- Claims a missing abstract method only fails when that method is called
- Cannot say which methods are abstract and which are mixins