What does types.MappingProxyType give you when you wrap a dict in it?
answer
- Read-only, and not a copy
- A window onto someone else's mapping
- Writes raise; mutating methods are missing
- types.MappingProxyType wraps a live mapping
- dict(proxy) when you need a snapshot
basics
~20 sA read-only window onto that same dict. Reads work as usual, but item assignment and deletion raise TypeError, and mutating methods such as update are absent. Nothing is copied, so later edits by the owner show through.
solid answer
~40 s`types.MappingProxyType(d)` returns a `mappingproxy` that forwards every read to `d` and refuses every write. `proxy[k] = v` and `del proxy[k]` raise `TypeError: 'mappingproxy' object does not support item assignment` (and the matching deletion message), while the mutating methods do not exist at all — `proxy.update(...)` is an `AttributeError`. Only `get`, `keys`, `values`, `items` and `copy` are exposed. It is a *view*, not a snapshot: the proxy keeps a reference to the original dict, so anything the owner writes into `d` afterwards is visible through the proxy immediately. `isinstance(proxy, dict)` is `False`, but it is registered as a `collections.abc.Mapping`, so annotate parameters that accept one as `Mapping`. `dict(proxy)` or `proxy.copy()` hands back an ordinary mutable dict.
code
pycon · 12 lines>>> from types import MappingProxyType
>>> config = {"region": "eu-west-1"}
>>> view = MappingProxyType(config)
>>> view["region"]
'eu-west-1'
>>> view["region"] = "us-east-1"
Traceback (most recent call last):
...
TypeError: 'mappingproxy' object does not support item assignment
>>> config["retries"] = 3
>>> dict(view)
{'region': 'eu-west-1', 'retries': 3}go deeper
Be ready to say what the one line does: types.MappingProxyType(d) hands a caller something they can read but not write. Remember that writing through it raises TypeError rather than silently doing nothing.
Explain that the proxy stores a reference, not a copy, so the owner's later writes show through, and that mutating methods are absent rather than overridden — proxy.update raises AttributeError while proxy[k] = v raises TypeError.
An interviewer expects you to place it in a design: expose module or service state to callers as a proxy instead of the dict itself, and be explicit that it prevents accidents rather than stopping a determined caller.
Own the API-surface question. Decide whether shared state leaves your boundary as a live proxy, a defensive copy or an immutable value type, and say out loud what you are promising callers about staleness and about who may mutate.
## What you actually get `types.MappingProxyType` is a thin wrapper around any object implementing the mapping protocol. `MappingProxyType(d)` stores a reference to `d` and returns an object that reprs as `mappingproxy({...})`. Every read — subscripting, `len()`, `in`, iteration, `get`, `keys`, `values`, `items`, equality — is forwarded to the wrapped mapping and behaves exactly as it would on the original. Every write is refused, in two different ways that are worth telling apart: * **Operators raise `TypeError`.** `proxy["k"] = 1` gives `TypeError: 'mappingproxy' object does not support item assignment`; `del proxy["k"]` gives the matching *item deletion* message. The syntax exists, the type declines to implement it. * **Mutating methods are simply absent.** `proxy.update({...})`, `proxy.pop("k")`, `proxy.clear()` and `proxy.setdefault(...)` raise `AttributeError`, because the proxy exposes only `get`, `keys`, `values`, `items` and `copy`. Code that duck-types with `hasattr` sees a different shape from a dict, which is occasionally what you want and occasionally a surprise. The constructor is picky: `MappingProxyType([1, 2])` raises `TypeError: mappingproxy() argument must be a mapping, not list`. Any mapping works, not only `dict` — including another `mappingproxy`, though wrapping a proxy in a proxy buys nothing. ## It is a view, so it is live The property interviewers probe: **the proxy does not copy**. It holds a reference to the original mapping. Whoever still owns that mapping mutates it freely, and every change is visible through the proxy at once. That is a feature — it is how you hand a component a live view of configuration that a reload can update underneath it — but it means "read-only" describes the *caller's* powers, not the data's stability. A caller who needs a stable snapshot must take one: `dict(proxy)` and `proxy.copy()` both return an ordinary, disconnected, mutable `dict`. Two copy traps worth remembering. `copy.copy(proxy)` and `copy.deepcopy(proxy)` both fail with `TypeError: cannot pickle 'mappingproxy' object`, because the proxy supports neither copying nor pickling and the fallback path goes through pickle; deep-copy the contents instead with `copy.deepcopy(dict(proxy))`. Similarly `json.dumps(proxy)` raises `TypeError: Object of type mappingproxy is not JSON serializable`, so serialize `dict(proxy)`. ## Typing and isinstance `isinstance(proxy, dict)` is `False` — `mappingproxy` is not a `dict` subclass. It *is* registered with `collections.abc.Mapping`, so `isinstance(proxy, Mapping)` is `True`. Annotate parameters as `Mapping[str, int]` rather than `dict[str, int]`: a signature that demands `dict` rejects the proxy under a type checker, and a runtime `isinstance(x, dict)` guard silently takes the wrong branch. Equality crosses the boundary happily — `proxy == {"a": 1}` is `True` when the contents match, because equality is delegated to the wrapped mapping. One more delegation to remember: `hash(proxy)` raises `TypeError: unhashable type: 'dict'`. The proxy is not a magic frozen dict; it forwards hashing to a mapping that is unhashable. If you need a hashable snapshot, build `frozenset(proxy.items())`, and only when every value is itself hashable. ## Where you meet one without asking for it The type is not exotic — you have already used it. A class namespace is handed out as one: `SomeClass.__dict__` and `vars(SomeClass)` both return a `mappingproxy`, which is why `SomeClass.__dict__["x"] = 1` raises `TypeError` while `SomeClass.x = 1` works. An instance's `__dict__` and a module's `__dict__` are ordinary dicts by contrast. ## The honest pitch in review `MappingProxyType` is cheap: one small wrapper object, no copy of the data, constant-time to create regardless of how large the mapping is. Its value is that it turns a class of accidents — a helper that "just" does `config["region"] = ...` — into an immediate, loud `TypeError` at the line that made the mistake, instead of a mystery three modules away. What it is not: a security boundary, a thread-safety mechanism, or a deep freeze. The wrapped dict remains reachable inside the process, concurrent readers can still observe a half-finished multi-key update, and mutable values behind the proxy stay mutable. When you reach for it, state which of those you are actually claiming.
- Can a mappingproxy be used as a dict key or put into a set?No. `hash()` on one raises `TypeError: unhashable type: 'dict'`, because the proxy delegates hashing to the mapping it wraps and dicts are unhashable. Being read-only through one interface does not make an object hashable. If you need a hashable snapshot of the contents, build `frozenset(proxy.items())`, and that only works while every value is itself hashable.
- How do you get an independent, mutable snapshot out of a mappingproxy?`dict(proxy)` or `proxy.copy()` — both return an ordinary `dict` that no longer tracks the original. Note that `copy.copy(proxy)` and `copy.deepcopy(proxy)` do not work: they fall back to pickling and raise `TypeError: cannot pickle 'mappingproxy' object`. If you want a deep snapshot, deep-copy the converted dict instead.
- Does a function annotated dict[str, str] accept a mappingproxy?Not under a type checker, and not under a runtime `isinstance(x, dict)` guard — `mappingproxy` is not a `dict` subclass. It is registered with `collections.abc.Mapping`, so annotate read-only parameters as `Mapping[str, str]`. That is the better annotation anyway: it documents that the function only reads.
A shop window: you see everything on the shelf and nothing you do to the glass changes the display — but the shopkeeper can restock at any moment and the view updates with it.
saying these in an interview costs you the question
- Says it copies the dict, so later edits are invisible
- Claims the wrapped dict becomes read-only for its owner too
- Expects isinstance(proxy, dict) to be True
- Thinks it makes concurrent access thread-safe
- Assumes json.dumps serializes a mappingproxy directly
- Calls it a frozen dict and expects it to be hashable