A “frozen” dictionary for Python
72 comments
·December 11, 2025pansa2
ndr
Immutability it's a joy to work with. Ask anyone who's worked with Clojure's dicts.
dkarl
It's interesting that he concludes that freezing dicts is "not especially useful" after addressing only a single motivation: the use of a dictionary as a key.
He doesn't address the reason that most of us in 2025 immediately think of, which is that it's easier to reason about code if you know that certain values can't change after they're created.
What a change in culture over the last 20 years!
mvanbaak
This was 19 (almost) 20 years ago. As stated in the lwn.net article, a lot of concurrency has been added to python, and it might now be time for something like a frozendict.
Things that were not useful in 2006 might be totally useful in 2026 ;P
Still, like you, I'm curious wether he has anything to say about it.
aewens
I think Raymond Hettinger is called out specially here because he did a well known talk called [Modern Dictionaries](https://youtu.be/p33CVV29OG8) where around 32:00 to 35:00 in he makes the quip about how younger developers think they need new data structures to handle new problems, but eventually just end up recreating / rediscovering solutions from the 1960s.
“What has been is what will be, and what has been done is what will be done; there is nothing new under the sun.”
sesm
Since that time HAMT was invented and successfully used in Scala and Clojure, so this talk didn't age well.
jonathaneunice
That's a great link and recommended reading.
It explains a lot about the design of Python container classes, and the boundaries of polymorphism / duck typing with them, and mutation between them.
I don't always agree with the choices made in Python's container APIs...but I always want to understand them as well as possible.
Also worth noting that understanding changes over time. Remember when GvR and the rest of the core developers argued adamantly against ordered dictionaries? Haha! Good times! Thank goodness their first wave of understanding wasn't their last. Concurrency and parallelism in Python was a TINY issue in 2006, but at the forefront of Python evolution these days. And immutability has come a long way as a design theme, even for languages that fully embrace stateful change.
zahlman
> Also worth noting that understanding changes over time. Remember when GvR and the rest of the core developers argued adamantly against ordered dictionaries? Haha! Good times!
The new implementation has saved space, but there are opportunities to save more space (specifically after deleting keys) that they've now denied themselves by offering the ordering guarantee.
jonathaneunice
Ordering, like stability in sorting, is an incredibly useful property. If it costs a little, then so be it.
This is optimizing for the common case, where memory is generally plentiful and dicts grow more than they shrink. Python has so many memory inefficiencies that occasional tombstones in the dict internal structure is unlikely to be a major effect. If you're really concerned, do `d = dict(d)` after aggressive deletion.
zahlman
> Another PEP 351 world view is that tuples can serve as frozenlists; however, that view represents a Liskov violation (tuples don't support the same methods). This idea resurfaces and has be shot down again every few months.
... Well, yes; it doesn't support the methods for mutation. Thinking of ImmutableFoo as a subclass of Foo is never going to work. And, indeed, `set` and `frozenset` don't have an inheritance relationship.
I normally find Hettinger very insightful so this one is disappointing. But nobody's perfect, and we change over time (and so do the underlying conditions). I've felt like frozendict was missing for a long time, though. And really I think the language would have been better with a more formal concept of immutability (e.g. linking it more explicitly to hashability; having explicit recognition of "cache" attributes, ...), even if it didn't go the immutable-by-default route.
kccqzy
Apple (or perhaps NeXT) has solved this problem already in Objective-C. Look at NSArray and NSMutableArray, or NSData and NSMutableData. It’s intuitive and Liskov-correct to make the mutable version a subclass of the immutable version. And it’s clearly wrong to have the subclass relationship the other way around.
Given how dynamic Python is, such a subclass relationship need not be evident at the C level. You can totally make one class whose implementation is independent of another class a subclass of the other, using PEP 3119. This gives implementations complete flexibility in how to implement the class while retaining the ontological subclass relationship.
pansa2
> ImmutableFoo as a subclass of Foo is never going to work. And, indeed, `set` and `frozenset` don't have an inheritance relationship.
Theoretically, could `set` be a subclass of `frozenset` (and `dict` of `frozendict`)? Do other languages take that approach?
> linking [immutability] more explicitly to hashability
AFAIK immutability and hashability are equivalent for the language's "core" types. Would it be possible to enforce that equivalence for user-defined types, given that mutability and the implementation of `__hash__` are entirely controlled by the programmer?
kccqzy
Yes you could. Other languages do. See NSMutableSet and NSSet in Objective-C.
chriswarbo
> Theoretically, could `set` be a subclass of `frozenset` (and `dict` of `frozendict`)?
At one extreme: sure, anything can be made a subclass of anything else, if we wanted to.
At the other extreme: no, since Liskov substitution is an impossibly-high bar to reach; especially in a language that's as dynamic/loose as Python. For example, consider an expression like '"pop" in dir(mySet)'
Strilanc
This is a type that I would use a lot.
For example, I often write classes that do cacheable analysis that results in a dict (e.g. the class stores a list of tiles defined by points and users want a point-to-tiles mapping for convenience). It's worth caching those transformations, e.g. using @functools.cached_property, but this introduces a risk where any caller could ruin the returned cached value by editing it. Currently, I take the safety hit (cache a dict) instead of the performance hit (make a new copy for each caller). Caching a frozendict would be a better tradeoff.
polyrand
A frozen dictionary would be very welcome. You can already do something similar using MappingProxyType [0]
from types import MappingProxyType
d = {}
d["a"] = 1
d["b"] = 2
print(d)
frozen = MappingProxyType(d)
print(frozen["a"])
# Error:
frozen["b"] = "new"
[0]: https://docs.python.org/3/library/types.html#types.MappingPr...code_biologist
Shoutout to pyrsistent, a personal favorite library for frozen/immutable/hashable data structures whenever I need to use lists, sets, or dicts as dict keys, or make sets of such items: https://github.com/tobgu/pyrsistent
sevensor
Concurrency is a good motivation, but this is super useful even in straight line code. There’s a huge difference between functions that might mutate a dictionary you pass in to them and functions that definitely won’t. Using Mapping is great, but it’s a shallow guarantee because you can violate it at run time.
sundarurfriend
Can someone ELI5 the core difference between this and named tuples, for someone who is not deep into Python? ChatGPT's answer boiled down to: unordered (this) vs ordered (NTs), "arbitrary keys, decided at runtime" vs "fixed set of fields decided at definition time" (can't an NT's keys also be interpolated from runtime values?), and a different API (`.keys()`, `.items()`), etc (I'm just giving this as context btw, no idea if there's inaccuracies in these).
So could this also have been approached from the other side, as in making unordered NamedTuples with support for the Mapping API? The line between dictionaries and named tuples and structs (across various languages) has always seemed a bit blurry to me, so I'm trying to get a better picture of it all through this.
_flux
The key difference is what the GPT outlined: tuples have order for the fields and named tuples are not indexed by the name, but instead a field accessor is used, i.e. foo.bar vs foo["bar"]. In addition namedtuples can be indexed using that order like tuples can (foo[0]), which clearly isn't possible with dicts and would be quite confusing if dict had integer key.
So I think the differences aren't great, but they are sufficient. A frozendict is not going to be indexable by an integer. Python already has an abstract type for this, for mostly the use of type checking I imagine: https://docs.python.org/3/glossary.html#term-mapping
Documentation for namedtuple: https://docs.python.org/3/library/collections.html#collectio...
quietbritishjim
> arbitrary keys, decided at runtime" vs "fixed set of fields decided at definition time" (can't an NT's keys also be interpolated from runtime values?)
If you want to create a named tuple with arbitrary field names at runtime then you need to create a new named tuple type before you create the instance. That is possible, since Python is a very dynamic language, but it's not particularly efficient and, more importantly, just feels a bit wrong. And are you going to somehow cache the types and reuse them if the field names match? It's all a bit of a mess compared to just passing the entries to the frozendict type.
willvarfar
The values in tuples cannot change. The values that keys point to in a frozen dict can?
But yeah I'd be in favour of something that looked a lot like a named tuple but with mutable values and supporting [name] access too. And of course some nice syntactic sugar rather like dicts and sets have with curly brackets today.
pansa2
> The values in tuples cannot change. The values that keys point to in a frozen dict can?
The entries of a tuple cannot be assigned to, but the values can be mutated. The same is true for a `frozendict` (according to the PEP they don't support `__setitem__`, but "values can be mutable").
vscode-rest
Tuple entries must be hashable, which (as far as standard library is concerned) means immutable.
grimgrin
I think you could have asked this same comment w/o mentioning ChatGPT and you wouldn't have been downvoted to oblivion in 3 minutes
I don't see anything wrong with your asking to understand
chistev
This place hates ChatGPT and AI. Lol.
Edit: Of course, I get down voted as I predicted I would. Lol.
acdha
This place hates laziness and imprecision. Using ChatGPT for editing or inspiration is okay as long as you personally review the results for accuracy and completeness, at which point people care about it as much as you announcing that you used a spell checker.
bilsbie
Wow weird Mandela effect for me. I really remember this being a built and actually using it.
aewens
You may be thinking of the `frozenset()` built in or the third party Python module [frozendict](https://pypi.org/project/frozendict/)?
Personally, I’ve been using a wrapper around `collections.namedtuple` as an underlying data structure to create frozen dictionaries when I’ve needed something like that for a project.
guidopallemans
When you are making str -> Any dictionaries it's quite likely you're better off with dataclasses or namedtuples anyway.
TheFlyingFish
That works if you're dealing with a known set of keys (i.e. what most statically-typed languages would call a struct). It falls down if you need something where the keys are unknowable until runtime, like a lookup table.
I do like dataclasses, though. I find them sneaking into my code more and more as time goes on. Having a declared set of properties is really useful, and it doesn't hurt either that they're syntactically nicer to use.
pansa2
There was a previous PEP (in 2012) with the exact same title:
https://peps.python.org/pep-0416/
Also one in 2019 for a "frozenmap":
Qem
Perhaps you used the frozen dict implementation from the pip installable boltons library: https://boltons.readthedocs.io/en/latest/dictutils.html#bolt...
hiddencost
Perhaps immutabledict?
mwsherman
I’ve found C#’s frozen dictionary to be useful: https://learn.microsoft.com/en-us/dotnet/api/system.collecti...
It’s optimized for fast reads in exchange for expensive creation.
ok123456
Ruby has had frozen dictionaries since version 1.0, and they are generally considered a good thing to use where possible.
codethief
Next step: Auto-inferring the correct (most narrow) TypedDict type from a frozendict. (Think `const foo = { … } as const` in TypeScript.) I miss this TS feature in Python on the daily.
anentropic
Agreed... but it shouldn't need a frozendict for this
IMHO TypedDict in Python are essentially broken/useless as is
What is needed is TS style structural matching, like a Protocol for dicts
mrweasel
What would that do exactly? Auto-generate a TypedDict class?
Bringing up TypedDict is sort of interesting, because it seems like a frozen dictionary could have been implemented by PEP 705, if typing.ReadOnly was enforced at runtime, and not just a hint to a static type checker.
dmurray
Auto-generate a TypedDict type while type checking, while doing nothing at runtime.
I expect this is not a very big ask and the various typecheckers will have versions of this soon after release.
mrweasel
Okay so basically just "inline":
class MyType(TypedDict):
a: str
b: int
or infer the type hint in: my_typed_dict: dict[str, int] = {"a": 5}
It should probably be something like: auto_typed: dict[typing.Const, typing.Const] = {"a": 5}
where typing.Const defaults to Any for an empty dict.adzm
Curious what this means for typescript as well; honestly I've only reached for as const rarely.
virtue3
I also SUPER LOVE this feature. Especially when you make a type union of the keys for easier indexing.
tweakimp
Can you give a Python example of a use case for this?
drhagen
If this gets wide enough use, they could add an optimization for code like this:
n = 1000
a = {}
for i in range(n):
a[i] = str(i)
a = frozendict(a) # O(n) operation can be turned to O(1)
It is relatively easy for the JIT to detect the `frozendict` constructor, the `dict` input, and the single reference immediately overwritten. Not sure if this would ever be worth it._flux
Wouldn't ref-counting CPython already know that a has a single reference, allowing this optimization without needing any particular smart JIT?
zbentley
I think GP was talking about optimizing away the O(N) call on the last line. The GC will take care of removing the reference to the old (mutable) dict, but constructing a new frozendict from a mutable dict would, in the current proposal, be an O(N) shallow copy.
There are also potentially other optimizations that could be applied (not specific to dict/frozendict) to reduce the memory overhead on operations like "a = f(a)" for selected values of "f".
zahlman
First thought: I would very much expect it to be able to do this optimization given the similar things it does for string concatenation.
But actually, I suspect it can't do this optimization simply because the name `frozendict` could be shadowed.
xamuel
This subject always seems to get bogged down in discussions about ordered vs. unordered keys, which to me seems totally irrelevant. No-one seems to mention the glaring shortcoming which is that, since dictionary keys are required to be hashable, Python has the bizarre situation where dicts cannot be dict keys, as in...
{{'foo': 'bar'}: 1, {3:4, 5:6}: 7}
...and there is no reasonable builtin way to get around this!
You may ask: "Why on earth would you ever want a dictionary with dictionaries for its keys?"
More generally, sometimes you have an array, and for whatever reason, it is convenient to use its members as keys. Sometimes, the array in question happens to be an array of dicts. Bang, suddenly it's impossible to use said array's elements as keys! I'm not sure what infuriates me more: said impossibility, or the python community's collective attitude that "that never happens or is needed, therefore no frozendict for you"
kzrdude
Turning a dictionary into a tuple of tuples `((k1, v1), (k2, v2), ...)`; isn't that a reasonable way?
If you want to have hash map keys, you need to think about how to hash them and how to compare for equality, it's just that. There will be complications to that such as floats, which have a tricky notion of equality, or in Python mutable collections which don't want to be hashable.
I wonder whether Raymond Hettinger has an opinion on this PEP. A long time ago, he wrote: "freezing dicts is a can of worms and not especially useful".
https://mail.python.org/pipermail/python-dev/2006-February/0...