# PEP 603: Adding a frozenmap type to collections

**URL:** <https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318>\
**Category:** PEPs\
**Created:** [September 12, 2019, 11:30am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318 "2019-09-12T11:30:33Z")\
**Posts on this page:** 15\
**Page:** 11

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [May 22, 2024, 8:29am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/207 "2024-05-22T08:29:01Z")

</div>

> [@alicederyn](#):
>
> I think you’d still need to hash each underlying map anyway just to check they were immutable? So this would be very expensive.

First hash would be expensive (as you say, in addition to calculating `hash(frozenset(d.items()))`, each underlying map also needs to be hashed to confirm it is immutable), but since a successful hash calculation indicates everything is immutable it would be OK to cache the result, making it O(1) after that. (the hash storage field would be excluded from both hashing and equality checks, so it can still be lazily calculated and stored as PEP 416 suggested for `frozendict`)

I don’t actually have a concrete use case, though (it was just an idle thought), so it might be better left out. The question just never came up before, since there weren’t any hashable mappings to be chained in the first place.

---

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [May 22, 2024, 8:44am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/208 "2024-05-22T08:44:23Z")

</div>

> [@Gouvernathor](#):
>
> I also noticed something : that PEP 416, which has not been mentioned here for a long time. If the resubmitted PEP just takes on `frozendict` and leaves the HAMT behind, then it’s just a reopening of 416 with slightly new arguments - and a new perspective on the persisting need for a `frozendict` after MappingProxyType’s integration.

The article at [The case for immutable dictionaries; and the central misunderstanding of PEP 351](https://web.archive.org/web/20210513100111/https://www.cs.toronto.edu/~tijmen/programming/immutableDictionaries.html) (linked earlier in the thread at its original, now 404, home) is genuinely interesting.

One valid point that it makes is that stronger static type analysis capabilities make explicit assertions about data structure immutability more beneficial. This is something worth bringing up in a revived proposal (even PEP 416 predates PEP 484 by a few years).

---

<div class="post-metadata">

**Author:** ![MegaIng](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/megaing/32/16162_2.png) [@MegaIng](https://discuss.python.org/u/MegaIng)\
**Post date:** [May 22, 2024, 10:05am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/209 "2024-05-22T10:05:57Z")

</div>

> [@ncoghlan](#):
>
> Tangential thought prompted by the above notes: PEP 603 should include making ChainMap hashable if all of the included mappings are hashable.

Nope, not possible, `.maps` is documented as a public, mutable list, meaning the `ChainMap` type can be mutable even if all mappings are not. This would require a new type, `FrozenChainMap`, but I’m my understanding that fills (almost) the same niche has `HAMT`? So I don’t think that’s worth aiming for.

---

<div class="post-metadata">

**Author:** ![Nineteendo](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nineteendo/32/19122_2.png) [@Nineteendo](https://discuss.python.org/u/Nineteendo)\
**Post date:** [May 22, 2024, 10:12am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/210 "2024-05-22T10:12:51Z")

</div>

> [@ncoghlan](#):
>
> Hence the suggestion of just naming it after the initial implementation technique, with a name like `collections.frozenhamt` or `collections.FrozenHAMT`.

A perfect example of this is “Segmentation fault”. We now use paging instead of segmentation, but the name wasn’t changed to “Paging fault”. So, I think this is fine.

---

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [May 23, 2024, 8:26am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/211 "2024-05-23T08:26:33Z")

</div>

> [@MegaIng](#):
>
> Nope, not possible, `.maps` is documented as a public, mutable list, meaning the `ChainMap` type can be mutable even if all mappings are not.

Ah, I had completely forgotten about that. You’re right, not worth worrying about in that case.

---

<div class="post-metadata">

**Author:** ![Gouvernathor](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/gouvernathor/32/7957_2.png) [@Gouvernathor](https://discuss.python.org/u/Gouvernathor)\
**Post date:** [July 14, 2024, 11:57am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/213 "2024-07-14T11:57:13Z")

</div>

> [@Gouvernathor](#):
>
> I also noticed something : that PEP 416, which has not been mentioned here for a long time. If the resubmitted PEP just takes on `frozendict` and leaves the HAMT behind, then it’s just a reopening of 416 with slightly new arguments - and a new perspective on the persisting need for a `frozendict` after MappingProxyType’s integration.  
> I wrote a draft for that, with just the required edits to make 416 up-to-date, but I don’t want to post it too early and steal Yury’s initiative.

Hi, considering that some time has passed without a new PEP draft being introduced, here is [the draft](https://gist.github.com/Gouvernathor/0955daab4cd520f3ba1689b50c4431a2) I mentioned there.

I named it `PEP 416-1`, but I’m sure that won’t last (maybe it will be the new 416, or the new 603 but it would be weird, or a new number entirely). So I’m not sure whether we should continue discussing this here, open a new thread or whatever else.

Feel free to give some feedback !

---

<div class="post-metadata">

**Author:** ![Jelle](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jelle/32/1049_2.png) [@Jelle](https://discuss.python.org/u/Jelle)\
**Post date:** [July 14, 2024, 2:58pm UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/214 "2024-07-14T14:58:19Z")

</div>

> [@Gouvernathor](#):
>
> maybe it will be the new 416, or the new 603 but it would be weird, or a new number entirely).

PEP editor opinion: This should almost certainly be a new PEP, or possibly a rewrite of PEP 603 if the author of that PEP agrees to it. PEP 416 is ancient history and should not be changed.

---

<div class="post-metadata">

**Author:** ![willofferfit](https://avatars.discourse-cdn.com/v4/letter/w/7feea3/32.png) [@willofferfit](https://discuss.python.org/u/willofferfit)\
**Post date:** [March 13, 2025, 2:05am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/216 "2025-03-13T02:05:19Z")

</div>

This is what I currently use due to the missing feature:

```python
from collections.abc import Hashable

type frozendict[K: Hashable, V: Hashable] = frozenset[tuple[K, V]]

def freeze_dict[K: Hashable, V: Hashable](d: dict[K, V]) -> frozendict[K, V]:
    return frozenset(d.items())

def unfreeze_dict[K: Hashable, V: Hashable](fd: frozendict[K, V]) -> dict[K, V]:
    d = dict(fd)
    assert len(d) == len(fd)
    return d

d = {1: 2, 3: 4}
fd = freeze_dict(d)
assert repr(d) == repr(unfreeze_dict(fd))
hash(fd) # works

```

For my use case (core algorithm that is part of a complex AI system), these were my requirements, from high to low priority:

1. Must be hashable and immutable
2. Must work for type annotations
3. Must be intuitive to use and understand, like dicts
4. Must have at least the same restrictions that dicts have (e.g. disallowing duplicate keys)
5. Must have a simple implementation and not be overengineered, to prevent bugs
6. Must easily support future Python versions
7. Where they’re used, must not be significantly slower than dicts

With these requirements, frozendict can be used in data structures that require hashable objects, and developer experience is as good as it can get. It would be great to have better time complexity, actual dict semantics, and a function like frozenset().

Over the years, I’ve often found myself needing to reimplement something like this. It’s time that we add frozendict to Python.

---

<div class="post-metadata">

**Author:** ![charles-cooper](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/charles-cooper/32/26704_2.png) [@charles-cooper](https://discuss.python.org/u/charles-cooper)\
**Post date:** [April 23, 2025, 9:31am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/217 "2025-04-23T09:31:36Z")

</div>

I think the assumptions for anybody reading `OrderedDict` is indeed that methods like `move_to_end()` have O(1) performance. While the exact implementation isn’t specified, algorithmic behavior is important to users

---

<div class="post-metadata">

**Author:** ![charles-cooper](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/charles-cooper/32/26704_2.png) [@charles-cooper](https://discuss.python.org/u/charles-cooper)\
**Post date:** [April 23, 2025, 9:50am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/218 "2025-04-23T09:50:38Z")

</div>

I’d like to chime in from a user’s point of view. I am the lead maintainer for the [Vyper compiler](https://github.com/vyperlang/vyper), and having an immutable map is extremely useful for certain algorithms. During analysis, there are a lot of analyses which maintain a dict (of whatever) at every point in the program. We usually do this as copy-on-write ([vyper/vyper/venom/analysis/liveness.py at d0e3e90e8ee6416fabb5df3f59dd6d17c7d1df04 · vyperlang/vyper · GitHub](https://github.com/vyperlang/vyper/blob/d0e3e90e8ee6416fabb5df3f59dd6d17c7d1df04/vyper/venom/analysis/liveness.py#L50)), and the performance is very sensitive to copy performance. Another example of such an analysis would be available expression analysis (I’d post a link, but as a new user, I am limited to 2 links per post)

I think the data structure is generally useful. Another example that comes to mind is a checkpointing dict – if you are implementing a kind of dict with journaling, with mutable hashmaps, at every checkpoint you need to either copy the current dict at checkpoint or rollback time. With a HAMT, it’s an O(1) reference copy.

Furthermore, I think the usefulness of the datastructure is already motivated by the fact that the standard library uses it already in `contextvars`.

The case for it shipping with CPython would be that it’s a generally useful data structure. For us, due to the risk of supply chain attacks, we prefer to keep dependencies as few as possible. While there is certainly the possibility of using a pypi package, e.g. `immutables` or `pyrsistent` each dependency adds some supply chain risk that we would prefer not to take on.

As for the naming, I think `collections.immutabledict` or `collections.immutablemap` signals the right thing – it’s a bit niche data structure, and it’s _immutable_ (in the functional data structures sense) rather than frozen, which to me just signals that it’s blocked from being mutated.

---

<div class="post-metadata">

**Author:** ![jamesdow21](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamesdow21/32/34808_2.png) [@jamesdow21](https://discuss.python.org/u/jamesdow21)\
**Post date:** [April 28, 2025, 5:33pm UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/219 "2025-04-28T17:33:14Z")

</div>

Having finally read through this thread\[1\], I thought I’d at least chime in with my own thoughts/wishes

Overall, my own ideal scenario if I had a magic wand and decision rights would be that both a PEP 416 style `frozendict` was added as a builtin and a PEP 603 style HAMT based immutable not-a-dict mapping was added to `collections`.

They both serve different purposes and would both be useful in different scenarios, and I imagine having just the HAMT based PEP 603 `frozenmap` would lead to inevitable confusion (seen a couple of times throughout this thread) that it works just like `dict` when that is not at all what is optimized for.

> [@ncoghlan](#):
>
> (Edit: after posting this, one possibility that did occur to me is `SnapshotMap`, since the point is that mutation is handled as a series of independent snapshots rather than via in-place mutation)

Alyssa’s suggestion of the name `SnapshotMap` is my personal favorite suggestion I’ve seen for the bikeshed-naming question and would fit in well alongside `ChainMap` in the `collections` module.

In my mind, it immediately evokes what the mapping is good at, i.e. creating space- and time-efficient snapshots of the mapping at different states. The fact that it’s a mapping that’s frozen/immutable isn’t the actual useful, interesting part, it’s that you can easily create modified versions and then rollback those changes to a previous version when needed.

The most compelling argument I’ve seen for it being in the standard library is that the implementation already is, so why not\[2\] give it a public API and let everyone benefit from the great work that was done in implementing contextvars.

Similarly, the most compelling argument I’ve seen for adding a builtin `frozendict` is that it feels like the odd one out of the builtin collections, with

- list: tuple
- set: frozenset
- dict: ???

Ideally it could reuse all of the implementation and optimizations that have gone into making `dict` efficient, keep all the same semantics as `dict` (e.g. O(1) lookup time, remembering insertion order, equality based on unordered keys and values), but just truly forbid anything that mutates it and add hashing support similar to how tuples work (i.e. hashable if all the values are hashable, raising a TypeError otherwise), then it seems like it would be a useful addition in some cases.

I personally don’t use frozensets very often, but there have been a couple of times where it’s fit a need perfectly and I’ve been very happy to have it, and I imagine that frozendicts would end up the the same

* * *

1. And hopefully not being too annoying by adding on again to an old and very long thread 

2. I know that is actually minimizing the additional maintenance and backwards-compatibility concerns of making something public, but I’ve got the magic wand to get exactly what _I_ personally want

---

<div class="post-metadata">

**Author:** ![blhsing](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/blhsing/32/25812_2.png) [@blhsing](https://discuss.python.org/u/blhsing)\
**Post date:** [April 29, 2025, 9:25am UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/220 "2025-04-29T09:25:48Z")

</div>

Inspired by [Add zero-copy conversion of `bytearray` to `bytes` by providing ` __bytes__ ()` - #18 by storchaka](https://discuss.python.org/t/add-zero-copy-conversion-of-bytearray-to-bytes-by-providing-bytes/79164/18), what I would love to have is a zero-copy `dict.freeze` method that constructs in O(1) time a `frozendict` instance, a hashable, picklable and immutable object that shares the same hash table with the original `dict`. This allows a dict to be efficiently made “gold” after it’s iteratively built.

And then one of the following two things can happen after `dict.freeze`:

1. The original `dict` gets cleared by getting a new allocation of an empty hash table if its reference count is 1. Make a copy if the reference count isn’t 1.
2. The original `dict` keeps a pointer to the `frozendict` such that if the `dict` ever gets mutated (via either Python or C API), the `frozendict` would then make a copy of the hash table and detaches.

To me option 1 would be good enough for my use cases, while option 2 offers more flexibility at the cost of an extra pointer per dict.

---

<div class="post-metadata">

**Author:** ![philzook58](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/philzook58/32/28019_2.png) [@philzook58](https://discuss.python.org/u/philzook58)\
**Post date:** [May 20, 2025, 12:17pm UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/221 "2025-05-20T12:17:01Z")

</div>

Chiming in. Having a persistent map type is something I want as a python user and have seen as an omission. The term I’ve searched for for such a thing is `frozendict` as a natural counterpart to my mind of the useful and interesting `frozenset`. I don’t really have a strong conception of the distinction between `dict` and other notions of mapping in python that seems to have motivated the name `frozenmap`.

I implement theorem proving algorithms that often involve backtracking with some environment mapping in [GitHub - philzook58/knuckledragger: A Low Barrier Proof Assistant](https://github.com/philzook58/knuckledragger) . I also have had to do a moderate amount of caching to improve performance. When I port things from the functional programming world, the lack of a persistent map type is felt. Basically, I get by by constantly copying dicts every time I use them and this is probably fine but makes me uneasy, and that uneasiness increases my cognitive load. I think it is a slight headwind to writing certain kinds of mathematical algorithms in python and gives the impression that the language is somewhat inhospitable to them. There are some experiments I haven’t even done because the copying feels too egregious. Perhaps taking on a persistent map dependency is acceptable to me.

I also felt the need for a hashable dictionary type in this blog post [A Python frozenset interpretation of Dependent Type Theory | Hey There Buddo!](https://www.philipzucker.com/frozenset_dtt/)

---

<div class="post-metadata">

**Author:** ![b3m2a1](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/b3m2a1/32/30469_2.png) [@b3m2a1](https://discuss.python.org/u/b3m2a1)\
**Post date:** [August 31, 2025, 7:39pm UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/222 "2025-08-31T19:39:00Z")

</div>

From my standpoint as a scientist who often has to deal with (potentially large) dicts to be merged/updated and where accidental mutations are a huge concern, I think _even if_ calling the `hamt.c` implementation a `frozenmap` ends up being the wrong term, having a `hamt` object exposed in the standard library, the same way there’s a `heapq` would be amazing and cut down on the burden of ensuring nothing breaks with yet another dependency. This is especially true since the implementation already exists in CPython and would only need the bindings to be exposed.

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [November 13, 2025, 5:31pm UTC](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318/223 "2025-11-13T17:31:34Z")

</div>

@corona10 and me propose something different: [PEP 814: Add frozendict built-in type](https://discuss.python.org/t/pep-814-add-frozendict-built-in-type/104854).

By the way, @yselivanov, do you plan to propose PEP 603 to the Steering Council?

[Previous page](https://discuss.python.org/t/pep-603-adding-a-frozenmap-type-to-collections/2318.md?page=10)
