This is a cached version of https://peps.python.org/pep-0814 from 2/28/2026, 3:28:14 PM.
PEP 814 – Add frozendict built-in type | peps.python.org
A new public immutable type frozendict is added to the builtins module.
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: Final Type: Standards Track Created: 12-Nov-2025 Python-Version: 3.15 Post-History: 13-Nov-2025 Resolution: 11-Feb-2026 Table of Contents Abstract Rationale Specification Construction Iteration Hashing and Comparison Union operators Copy Typing Representation C API Differences between dict and frozendict Possible candidates for frozendict in the stdlib Python modules Extension modules Relationship to PEP 416 frozendict Relationship to PEP 603 frozenmap Reference Implementation Thread Safety Rejected Ideas Inherit from dict Deferred Ideas New syntax for frozendict literals Method to convert dict to frozendict Type annotation References Acknowledgements Copyright Important This PEP is a historical document. The up-to-date, canonical documentation can now be found at frozendict. × See PEP 1 for how to propose changes. 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 https://github.com/python/cpython/pull/141508 frozendict shares most of its code with the dict type. Add PyFrozenDictObject structure which inherits from PyDictObject and has an additional ma_hash member. 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 PEP 416 (frozendict) PEP 603 (collections.frozenmap) Acknowledgements This PEP is based on prior work from Yury Selivanov (PEP 603). Copyright This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive. Source: https://github.com/python/peps/blob/main/peps/pep-0814.rst Last modified: 2026-02-21 20:35:40 GMT