Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 814 – Add frozendict built-in type

PEP 814 – Add frozendict built-in type

Author:
Victor Stinner <vstinner at python.org>, Donghee Na <donghee.na at python.org>
Discussions-To:
Discourse thread
Status:
Accepted
Type:
Standards Track
Created:
12-Nov-2025
Python-Version:
3.15
Post-History:
13-Nov-2025
Resolution:
11-Feb-2026

Table of Contents

Abstract

A new public immutable type frozendict is added to the builtins module.

We expect frozendict to be safe by design, as it prevents any unintended modifications. This addition benefits not only CPython’s standard library, but also third-party maintainers who can take advantage of a reliable, immutable dictionary type.

Rationale

The proposed frozendict type:

  • implements the collections.abc.Mapping protocol,
  • supports pickling.

The following use cases illustrate why an immutable mapping is desirable:

  • Immutable mappings are hashable which allows their use as dictionary keys or set elements.
  • This hashable property permits functions decorated with @functools.lru_cache() to accept immutable mappings as arguments. Unlike an immutable mapping, passing a plain dict to such a function results in error.
  • Using an immutable mapping as a function parameter’s default value avoids the problem of mutable default values.

There are already third-party frozendict and frozenmap packages available on PyPI, proving that there is demand for immutable mappings.

Specification

A new public immutable type frozendict is added to the builtins module. It is not a dict subclass but inherits directly from object.

Construction

frozendict implements a dict-like construction API:

  • frozendict() creates a new empty immutable mapping.
  • frozendict(**kwargs) creates a mapping from **kwargs, e.g. frozendict(x=1, y=2).
  • frozendict(collection) creates a mapping from the passed collection object. The passed collection object can be:
    • a dict,
    • another frozendict,
    • or an iterable of key/value tuples.
  • frozendict(collection, **kwargs) combines the two previous constructions.

Keys must be hashable, but values can be non-hashable. Using hashable values creates a hashable frozendict.

Creating a frozendict from a dict, frozendict(dict), has a complexity of O(n): items are copied (shallow copy).

The insertion order is preserved.

Iteration

As frozendict implements the standard collections.abc.Mapping protocol, so all expected methods of iteration are supported:

assert list(m.items()) == [('foo', 'bar')]
assert list(m.keys()) == ['foo']
assert list(m.values()) == ['bar']
assert list(m) == ['foo']

Iterating on frozendict, as on dict, uses the insertion order.

Hashing and Comparison

frozendict instances can be hashable just like tuple objects:

hash(frozendict(foo='bar'))  # works
hash(frozendict(foo=['a', 'b', 'c']))  # error, list is not hashable

The hash value does not depend on the items’ order. It is computed on keys and values. Pseudo-code of hash(frozendict):

hash(frozenset(frozendict.items()))

Equality test does not depend on the items’ order either. Example:

>>> a = frozendict(x=1, y=2)
>>> b = frozendict(y=2, x=1)
>>> hash(a) == hash(b)
True
>>> a == b
True

It’s possible to compare frozendict to dict. Example:

>>> frozendict(x=1, y=2) == dict(x=1, y=2)
True

Union operators

It’s possible to join two frozendict, or a frozendict with a dict, with the merge (|) operator. Example:

>>> frozendict(x=1) | frozendict(y=1)
frozendict({'x': 1, 'y': 1})
>>> frozendict(x=1) | dict(y=1)
frozendict({'x': 1, 'y': 1})

If some keys are in common, the values of the right operand are taken:

>>> frozendict(x=1, y=2) | frozendict(y=5)
frozendict({'x': 1, 'y': 5})

The update operator |= does not modify a frozendict in-place, but creates a new frozendict:

>>> d = frozendict(x=1)
>>> copy = d
>>> d |= frozendict(y=2)
>>> d
frozendict({'x': 1, 'y': 2})
>>> copy   # left unchanged
frozendict({'x': 1})

See also PEP 584 “Add Union Operators To dict”.

Copy

frozendict.copy() returns a shallow copy. In CPython, it simply returns the same frozendict (new reference).

Use copy.deepcopy() to get a deep copy.

Example:

>>> import copy
>>> d = frozendict(mutable=[])
>>> shallow_copy = d.copy()
>>> deep_copy = copy.deepcopy(d)
>>> d['mutable'].append('modified')
>>> d
frozendict({'mutable': ['modified']})
>>> shallow_copy  # modified!
frozendict({'mutable': ['modified']})
>>> deep_copy     # unchanged
frozendict({'mutable': []})

Typing

It is possible to use the standard typing notation for frozendicts:

m: frozendict[str, int] = frozendict(x=1)

Representation

frozendict will not use a special syntax for its representation. The repr() of a frozendict instance looks like this:

>>> frozendict(x=1, y=2)
frozendict({'x': 1, 'y': 2})

C API

Add the following APIs:

  • PyAnyDict_Check(op) macro
  • PyAnyDict_CheckExact(op) macro
  • PyFrozenDict_Check() macro
  • PyFrozenDict_CheckExact() macro
  • PyFrozenDict_New(collection) function
  • PyFrozenDict_Type

Even if frozendict is not a dict subclass, it can be used with PyDict_GetItemRef() and similar “PyDict_Get” functions.

Passing a frozendict to PyDict_SetItem() or PyDict_DelItem() fails with TypeError. PyDict_Check() on a frozendict is false.

Differences between dict and frozendict

  • dict has more methods than frozendict:
    • __delitem__(key)
    • __setitem__(key, value)
    • clear()
    • pop(key)
    • popitem()
    • setdefault(key, value)
    • update(*args, **kwargs)
  • A frozendict can be hashed with hash(frozendict) if all keys and values can be hashed.

Possible candidates for frozendict in the stdlib

We have identified several stdlib modules where adopting frozendict can enhance safety and prevent unintended modifications by design. We also believe that there are additional potential use cases beyond the ones listed below. However, this does not mean that we intend to make these changes without the approval of the respective module maintainers.

Note: it remains possible to bind again a variable to a new modified frozendict or a new mutable dict.

Python modules

Replace dict with frozendict in function results:

  • email.headerregistry: ParameterizedMIMEHeader.params() (replace MappingProxyType)
  • enum: EnumType.__members__() (replace MappingProxyType)

Replace dict with frozendict for constants:

  • _opcode_metadata: _specializations, _specialized_opmap, opmap
  • _pydatetime: specs (in _format_time())
  • _pydecimal: _condition_map
  • bdb: _MonitoringTracer.EVENT_CALLBACK_MAP
  • dataclasses: _hash_action
  • dis: deoptmap, COMPILER_FLAG_NAMES
  • functools: _convert
  • gettext: _binary_ops, _c2py_ops
  • imaplib: Commands, Mon2num
  • json.decoder: _CONSTANTS, BACKSLASH
  • json.encoder: ESCAPE_DCT
  • json.tool: _group_to_theme_color
  • locale: locale_encoding_alias, locale_alias, windows_locale
  • opcode: _cache_format, _inline_cache_entries
  • optparse: _builtin_cvt
  • platform: _ver_stages, _default_architecture
  • plistlib: _BINARY_FORMAT
  • ssl: _PROTOCOL_NAMES
  • stringprep: b3_exceptions
  • symtable: _scopes_value_to_name
  • tarfile: PAX_NUMBER_FIELDS, _NAMED_FILTERS
  • token: tok_name, EXACT_TOKEN_TYPES
  • tomllib._parser: BASIC_STR_ESCAPE_REPLACEMENTS
  • typing: _PROTO_ALLOWLIST

Accept frozendict type:

  • builtins: eval() and exec() (globals argument)

Extension modules

Replace dict with frozendict for constants:

  • errno: errorcode

Relationship to PEP 416 frozendict

Since 2012 (PEP 416), the Python ecosystem has evolved:

  • asyncio was added in 2014 (Python 3.4)
  • Free threading was added in 2024 (Python 3.13)
  • concurrent.interpreters was added in 2025 (Python 3.14)

There are now more use cases to share immutable mappings.

frozendict now preserves the insertion order, whereas PEP 416 frozendict was unordered (as PEP 603 frozenmap). frozendict relies on the dict implementation which preserves the insertion order since Python 3.6.

The first motivation to add frozendict was to implement a sandbox in Python. It’s no longer the case in this PEP.

types.MappingProxyType was added in 2012 (Python 3.3). This type is not hashable and it’s not possible to inherit from it. It’s also easy to retrieve the original dictionary which can be mutated, for example using gc.get_referents().

Relationship to PEP 603 frozenmap

collections.frozenmap has different properties than frozendict:

  • frozenmap items are unordered, whereas frozendict preserves the insertion order.
  • frozenmap has additional methods:
    • including(key, value)
    • excluding(key)
    • union(mapping=None, **kw)

    These methods to mutate a frozenmap have a complexity of O(1).

  • A mapping lookup (mapping[key]) has a complexity of O(log n) with frozenmap and a complexity of O(1) with frozendict.

Reference Implementation

Thread Safety

Once a frozendict is created, its shallow immutability is guaranteed. This means it can be safely shared between threads without synchronization, as long as its values are not modified by other threads.

Rejected Ideas

Inherit from dict

If frozendict inherits from dict, it would become possible to call dict methods to mutate an immutable frozendict. For example, it would be possible to call dict.__setitem__(frozendict, key, value).

It may be possible to prevent modifying frozendict using dict methods, but that would require to explicitly exclude frozendict which can affect dict performance. Also, there is a higher risk of forgetting to exclude frozendict in some methods.

If frozendict does not inherit from dict, there is no such issue.

Deferred Ideas

New syntax for frozendict literals

Various syntaxes have been proposed to write frozendict literals.

A new syntax can be added later if needed.

Method to convert dict to frozendict

Different methods have been proposed to convert a mutable dict to an immutable frozendict with O(1) complexity, such as dict.freeze(). The idea would be to move dict contents into frozendict: it would make the dict empty. Another idea would be to use “copy-on-write”: only copy the dict at its first modification.

We consider that such method can be added later if needed, but it doesn’t have to be added right now. Moreover, if such method is added, it would be nice to add a similar method for list/tuple and set/frozenset. See also PEP 351 (Freeze protocol).

Type annotation

It has been proposed to add class TD(TypedDict, frozen=True) or Frozen[MyTypedDict] to define a frozendict type.

We consider that such type can be added later if needed.

References

Acknowledgements

This PEP is based on prior work from Yury Selivanov (PEP 603).


Source: https://github.com/python/peps/blob/main/peps/pep-0814.rst

Last modified: 2026-01-23 22:49:04 GMT