Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 79 additions & 10 deletions peps/pep-0814.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Status: Draft
Type: Standards Track
Created: 12-Nov-2025
Python-Version: 3.15
Post-History: `13-Nov-2025 <https://discuss.python.org/t/104854>`__


Abstract
========
Expand Down Expand Up @@ -75,6 +77,15 @@ Construction
- another ``frozendict``,
- or an iterable of key/value tuples.

* ``frozendict(collection, **kwargs)`` combines the two previous
constructions.

Keys must be hashable and so immutable, but values can be mutable.
Using immutable values creates a hashtable ``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.


Expand All @@ -92,8 +103,8 @@ protocol, so all expected methods of iteration are supported::
Iterating on ``frozendict``, as on ``dict``, uses the insertion order.


Hashing
-------
Hashing and Comparison
----------------------

``frozendict`` instances can be hashable just like tuple objects::

Expand All @@ -114,6 +125,64 @@ Equality test does not depend on the items' order either. Example::
>>> 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
----

``frozencopy.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
------
Expand All @@ -138,10 +207,12 @@ C API

Add the following APIs:

* ``PyFrozenDict_Type``
* ``PyFrozenDict_New(collection)`` function
* ``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.
Expand Down Expand Up @@ -269,12 +340,10 @@ Relationship to PEP 603 frozenmap
* ``excluding(key)``
* ``union(mapping=None, **kw)``

========== ============== ==============
Complexity ``frozenmap`` ``frozendict``
========== ============== ==============
Lookup *O*\ (log *n*) *O*\ (1)
Copy *O*\ (1) *O*\ (*n*)
========== ============== ==============
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
Expand Down