I’ve got clear use cases for frozen dicts, based on an “ownership model” that ensures you don’t have to think about cache corruption because it won’t happen.
I don’t think it deserves a negative connotation. It’s more worry-free programming than anything else.
The core semi-conflicting axioms are these:
- you don’t have ownership over objects passed into a function as arguments - you’re merely borrowing them
- you give ownership of any objects that you return from a function
(there’s also some rules to do with classes)
This is a ‘problem’ that can be illustrated by a very simple function:
def f(x): return x
Here f does not have ownership over x, but f gives away ownership over x.
The dogmatic approach would be to define instead
def f(x): return deepcopy(x)
but that’s doing extra compute, and often without any benefit, because the place that’s passing in the arguments is usually already ‘owner’ of the object it’s passing in, or maybe it’s the case that the thing being passed into the function is a newly created objects, or sometimes there’s some kind of guarantee that the receiver of x won’t change it.
So I have to think about information flows, and I don’t like it when I’m forced to think. Especially about information flows, because those are non-local by their very nature.
All of this thought can be avoided if x is of a frozen type.
There is no critical need, because 80% of the time we can resolve this with frozen dataclasses.
All that said, I’d vastly prefer a better frozendict constructor to a dict → frozendict method.
I think all the places I’ve needed/wanted frozen dicts so far, it’s been possible to write the dict using a single dict expression, and I think a dict expression that starts with f{, frozen{ or frozendict{ looks significantly better that a dict expression that ends with }.take_frozendict(), especially if the dict expression takes several lines.