Python Basics: Expose a Read-Only Mapping View
An API can expose configuration keys for reading without allowing callers to assign through that mapping. MappingProxyType provides a live read-only view. It does not freeze the original dictionary or the objects stored inside it.
Prepare a scratch directory
Tested on Linux with CPython 3.12.14. You need the named runtime, Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. Run Python without -O so the assertions remain enabled. Any generated fixture files live inside an automatically cleaned temporary directory.
Keep using the same terminal. Stop if setup fails, before saving the source:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Run the example
Save this as demonstrate-mapping-proxy.py:
from types import MappingProxyType
settings = {"mode": "preview", "labels": ["draft"]}
view = MappingProxyType(settings)
assert view["mode"] == "preview"
try:
view["mode"] = "ready"
except TypeError:
print("Assignment through view: blocked")
else:
raise AssertionError("View allowed assignment")
snapshot = dict(view)
settings["mode"] = "ready"
assert view["mode"] == "ready"
assert snapshot["mode"] == "preview"
print("Live mode:", view["mode"])
print("Copied mode:", snapshot["mode"])
view["labels"].append("review")
assert settings["labels"] == ["draft", "review"]
assert snapshot["labels"] is settings["labels"]
print("Nested list remains mutable:", settings["labels"])Run it:
python3 -B demonstrate-mapping-proxy.pyObserved output:
Assignment through view: blocked
Live mode: ready
Copied mode: preview
Nested list remains mutable: ['draft', 'review']Read the result
The proxy blocks item assignment through its own interface. The owner can still update the underlying dictionary, and the view reflects that update immediately. dict(view) copies the mapping entries, so its scalar mode value stays unchanged. Both mappings still refer to the same nested list, which makes the shallow-copy boundary visible.
Apply it carefully
A mapping proxy is an access-interface choice, not a deep immutability or synchronization mechanism. Use immutable nested values when callers must not mutate them, and choose an explicit snapshot strategy when later owner changes should not be visible. A shallow dictionary copy is enough only when sharing the values is acceptable. Concurrent mutation can also affect iteration; a read-only view does not make a changing dictionary thread-safe.
The official reference describes the API and its options. The checks above cover the specific fixture, not every possible input or environment.
Clean up
Remove the source and then the empty practice directory:
rm -- demonstrate-mapping-proxy.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf the directory is not empty, inspect the remaining files rather than deleting unknown contents.
Acknowledgements
This post was created with assistance from AI.