Python already has a bunch of immutable / “frozen-ish” data structures, just not a first-class frozendict (yet). Some are true containers like tuple and frozenset, others are read-only views or “frozen” user-defined records.
Here’s the landscape.
These are the big ones everyone leans on already:
tuple
Ordered, indexable, hashable if all elements are hashable.
Workhorse for “frozen list” and composite keys.
frozenset
Unordered, hashable set.
Used for keys, members of other sets, or when you want a set but must guarantee no mutation.
str
Immutable sequence of Unicode code points.
Used constantly as “small immutable containers” of text.
bytes
Immutable sequence of bytes.
Often used as immutable “blobs,” protocol messages, cache keys, etc.
range
Immutable, lazy arithmetic sequence; behaves like a read-only sequence.
Size, start, step are fixed, contents are virtual.
memoryview (read-only)
If you create a memoryview over a read-only buffer, it is effectively immutable.
These are all actual objects that cannot be mutated once created.
Even before a frozendict, there is an immutable dict-like thing:
types.MappingProxyType
xfrom types import MappingProxyType
d = {"a": 1, "b": 2}m = MappingProxyType(d)
m["a"] # 1m["a"] = 2 # TypeError: 'mappingproxy' object does not support item assignmentGives a read-only view of a dict.
The underlying dict can still change if you hold a reference to it, so it’s not “persistent,” just non-mutating from the outside.
Very handy for exposing module-level or class-level registries safely.
There are also APIs that return immutable mapping-like objects, even if the type is not directly constructible:
importlib.metadata.entry_points() (newer versions) returns an object that behaves like an immutable mapping/sequence (dict-like, but read-only).
Some internal mappings in stdlib are exposed as read-only (e.g., some attributes on modules, sys.implementation, etc.), but they’re not general-purpose containers.
These are meant for user-defined structured data, not arbitrary mappings, but they’re still immutable containers of a sort:
dataclasses.dataclass(frozen=True)
xxxxxxxxxxfrom dataclasses import dataclass
(frozen=True)class Point: x: int y: int
p = Point(1, 2)# p.x = 3 # FrozenInstanceErrorPrevents attribute reassignment.
You can still smuggle in mutable fields if you want (list, dict), so it’s “shallow” immutability.
typing.NamedTuple / collections.namedtuple
xxxxxxxxxxfrom typing import NamedTuple
class Point(NamedTuple): x: int y: intInstances are tuples under the hood, so they are strictly immutable.
Again, shallow: fields can refer to mutable structures.
Enums (enum.Enum)
Individual enum members are singletons and immutable.
The Enum class itself is not a “container” in the same way, but it is a kind of frozen mapping from names to values.
These are strongly used as “frozen structs” more than as general purpose containers.
Not containers, but they behave like immutable data types:
datetime / date / time
All operations create new objects, no in-place mutation.
decimal.Decimal
Also immutable numeric value type.
fractions.Fraction
Rational numbers, immutable.
These matter when you want functional-style programming or safe sharing across threads.
For “real” immutable data structures in the functional-programming sense (structural sharing, cheap copies), you’re typically looking at external libraries:
immutables (immutables.Map)A popular high-performance immutable mapping used by things like HTTPX, Starlette, and friends.
Behaves a lot like a dictionary but:
You “modify” it by creating new maps that share structure internally.
xxxxxxxxxxfrom immutables import Map
m1 = Map({"a": 1})m2 = m1.set("b", 2)# m1 is unchanged; m2 has {"a": 1, "b": 2}pyrsistentA full suite of persistent data types:
PVector (list-like)
PMap (dict-like)
PSet (set-like)
plus records, typed structures, etc.
Emphasizes functional, persistent data structures with structural sharing.
attrs with frozen=TrueSimilar to dataclasses, but with a richer ecosystem and features.
Used a ton in larger Python codebases that want reliable “frozen config objects.”
These libraries are the spiritual ancestors of a future frozendict in the stdlib: they show the ergonomics and performance characteristics people care about.
So conceptually:
MappingProxyType
Read-only view of a mutable dict.
Great for exposing stable interfaces, but the underlying dict can still be modified.
Hypothetical frozendict
Actually immutable like tuple/frozenset.
Likely hashable if all keys/values are hashable, so usable as dict keys or set members.
Would fill the missing space in the “core immutable containers” grid:
list → tuple
set → frozenset
dict → frozendict (proposed)
Right now, if I want an honest-to-goodness immutable dict-like structure without third party dependencies, the closest is:
Create a MappingProxyType over a dict you then discard your reference to.
Or roll your own tiny wrapper around a tuple of key/value pairs plus some lookup logic (less ergonomic).
When you think “immutable / frozen” in Python, it’s useful to sort things into:
Primitive immutable containers
tuple, frozenset, str, bytes, range, (sometimes) memoryview.
Read-only views
types.MappingProxyType, various API-returned mappings.
Frozen user-defined records
dataclass(frozen=True), NamedTuple, attrs(frozen=True), Enum.
Persistent/functional structures (third party)
immutables.Map, pyrsistent.PMap / PVector / PSet, etc.
A frozendict would just complete the “primitive immutable containers” story, which is why so many people are excited about it.