Skip to content

Commit 35b55fa

Browse files
authored
Merge branch 'main' into pep-811-core-team-approval-process
2 parents b27053c + 67b709a commit 35b55fa

3 files changed

Lines changed: 354 additions & 3 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@ peps/pep-0807.rst @dstufft
687687
peps/pep-0809.rst @zooba
688688
peps/pep-0810.rst @pablogsal @DinoV @Yhg1s
689689
peps/pep-0811.rst @sethmlarson @gpshead
690+
peps/pep-0814.rst @vstinner @corona10
690691
# ...
691692
peps/pep-2026.rst @hugovk
692693
# ...

peps/pep-0545.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,9 +322,11 @@ and http://zanata.org/.
322322
python-docs-translations
323323
''''''''''''''''''''''''
324324

325-
The `python-docs-translations GitHub organization <https://github.com/python-docs-translations>`_
326-
is home to several useful translation tools such as the translations
327-
`dashboard <https://github.com/python-docs-translations/dashboard>`_.
325+
The `python-docs-translations GitHub organization <https://github.com/python-docs-translations>`__
326+
is home to several useful translation tools including
327+
`translations.python.org <https://translations.python.org>`__
328+
(`python-docs-translations/dashboard
329+
<https://github.com/python-docs-translations/dashboard>`__).
328330

329331

330332
Documentation Contribution Agreement

peps/pep-0814.rst

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
PEP: 814
2+
Title: Add frozendict built-in type
3+
Author: Victor Stinner <vstinner@python.org>, Donghee Na <donghee.na@python.org>
4+
Discussions-To: https://discuss.python.org/t/104854
5+
Status: Draft
6+
Type: Standards Track
7+
Created: 12-Nov-2025
8+
Python-Version: 3.15
9+
10+
Abstract
11+
========
12+
13+
A new public immutable type ``frozendict`` is added to the ``builtins``
14+
module.
15+
16+
We expect ``frozendict`` to be safe by design, as it prevents any unintended
17+
modifications. This addition benefits not only CPython’s standard
18+
library, but also third-party maintainers who can take advantage of a
19+
reliable, immutable dictionary type.
20+
21+
22+
Rationale
23+
=========
24+
25+
The proposed ``frozendict`` type:
26+
27+
* implements the ``collections.abc.Mapping`` protocol,
28+
* supports pickling.
29+
30+
The following use cases illustrate why an immutable mapping is
31+
desirable:
32+
33+
* Immutable mappings are hashable which allows their use as dictionary
34+
keys or set elements.
35+
36+
* This hashable property permits functions decorated with
37+
``@functools.lru_cache()`` to accept immutable mappings as arguments.
38+
Unlike an immutable mapping, passing a plain ``dict`` to such a function
39+
results in error.
40+
41+
* Using an immutable mapping as a function parameter's default value
42+
avoids the problem of mutable default values.
43+
44+
* Immutable mappings can be used to safely share dictionaries across
45+
thread and asynchronous task boundaries. The immutability makes it
46+
easier to reason about threads and asynchronous tasks.
47+
48+
There are already third-party ``frozendict`` and ``frozenmap`` packages
49+
available on PyPI, proving that there is demand for
50+
immutable mappings.
51+
52+
53+
Specification
54+
=============
55+
56+
A new public immutable type ``frozendict`` is added to the ``builtins``
57+
module. It is not a ``dict`` subclass but inherits directly from
58+
``object``.
59+
60+
61+
Construction
62+
------------
63+
64+
``frozendict`` implements a ``dict``-like construction API:
65+
66+
* ``frozendict()`` creates a new empty immutable mapping.
67+
68+
* ``frozendict(**kwargs)`` creates a mapping from ``**kwargs``,
69+
e.g. ``frozendict(x=1, y=2)``.
70+
71+
* ``frozendict(collection)`` creates a mapping from the passed
72+
collection object. The passed collection object can be:
73+
74+
- a ``dict``,
75+
- another ``frozendict``,
76+
- or an iterable of key/value tuples.
77+
78+
The insertion order is preserved.
79+
80+
81+
Iteration
82+
---------
83+
84+
As ``frozendict`` implements the standard ``collections.abc.Mapping``
85+
protocol, so all expected methods of iteration are supported::
86+
87+
assert list(m.items()) == [('foo', 'bar')]
88+
assert list(m.keys()) == ['foo']
89+
assert list(m.values()) == ['bar']
90+
assert list(m) == ['foo']
91+
92+
Iterating on ``frozendict``, as on ``dict``, uses the insertion order.
93+
94+
95+
Hashing
96+
-------
97+
98+
``frozendict`` instances can be hashable just like tuple objects::
99+
100+
hash(frozendict(foo='bar')) # works
101+
hash(frozendict(foo=['a', 'b', 'c'])) # error, list is not hashable
102+
103+
The hash value does not depend on the items' order. It is computed on
104+
keys and values. Pseudo-code of ``hash(frozendict)``::
105+
106+
hash(frozenset(frozendict.items()))
107+
108+
Equality test does not depend on the items' order either. Example::
109+
110+
>>> a = frozendict(x=1, y=2)
111+
>>> b = frozendict(y=2, x=1)
112+
>>> hash(a) == hash(b)
113+
True
114+
>>> a == b
115+
True
116+
117+
118+
Typing
119+
------
120+
121+
It is possible to use the standard typing notation for ``frozendict``\ s::
122+
123+
m: frozendict[str, int] = frozendict(x=1)
124+
125+
126+
Representation
127+
--------------
128+
129+
``frozendict`` will not use a special syntax for its representation.
130+
The ``repr()`` of a ``frozendict`` instance looks like this:
131+
132+
>>> frozendict(x=1, y=2)
133+
frozendict({'x': 1, 'y': 2})
134+
135+
136+
C API
137+
-----
138+
139+
Add the following APIs:
140+
141+
* ``PyFrozenDict_Type``
142+
* ``PyFrozenDict_New(collection)`` function
143+
* ``PyFrozenDict_Check()`` macro
144+
* ``PyFrozenDict_CheckExact()`` macro
145+
146+
Even if ``frozendict`` is not a ``dict`` subclass, it can be used with
147+
``PyDict_GetItemRef()`` and similar "PyDict_Get" functions.
148+
149+
Passing a ``frozendict`` to ``PyDict_SetItem()`` or ``PyDict_DelItem()``
150+
fails with ``TypeError``. ``PyDict_Check()`` on a ``frozendict`` is
151+
false.
152+
153+
Exposing the C API helps authors of C extensions supporting
154+
``frozendict`` when they need to support thread-safe immutable
155+
containers. It will be important since
156+
:pep:`779` (Criteria for supported status for free-threaded Python) was
157+
accepted, people need this for their migration.
158+
159+
160+
Differences between ``dict`` and ``frozendict``
161+
===============================================
162+
163+
* ``dict`` has more methods than ``frozendict``:
164+
165+
* ``__delitem__(key)``
166+
* ``__setitem__(key, value)``
167+
* ``clear()``
168+
* ``pop(key)``
169+
* ``popitem()``
170+
* ``setdefault(key, value)``
171+
* ``update(*args, **kwargs)``
172+
173+
* A ``frozendict`` can be hashed with ``hash(frozendict)`` if all keys
174+
and values can be hashed.
175+
176+
177+
Possible candidates for ``frozendict`` in the stdlib
178+
====================================================
179+
180+
We have identified several stdlib modules where adopting ``frozendict``
181+
can enhance safety and prevent unintended modifications by design. We
182+
also believe that there are additional potential use cases beyond the
183+
ones listed below.
184+
185+
Note: it remains possible to bind again a variable to a new modified
186+
``frozendict`` or a new mutable ``dict``.
187+
188+
Python modules
189+
--------------
190+
191+
Replace ``dict`` with ``frozendict`` in function results:
192+
193+
* ``email.headerregistry``: ``ParameterizedMIMEHeader.params()``
194+
(replace ``MappingProxyType``)
195+
* ``enum``: ``EnumType.__members__()`` (replace ``MappingProxyType``)
196+
197+
Replace ``dict`` with ``frozendict`` for constants:
198+
199+
* ``_opcode_metadata``: ``_specializations``, ``_specialized_opmap``,
200+
``opmap``
201+
* ``_pydatetime``: ``specs`` (in ``_format_time()``)
202+
* ``_pydecimal``: ``_condition_map``
203+
* ``bdb``: ``_MonitoringTracer.EVENT_CALLBACK_MAP``
204+
* ``dataclasses``: ``_hash_action``
205+
* ``dis``: ``deoptmap``, ``COMPILER_FLAG_NAMES``
206+
* ``functools``: ``_convert``
207+
* ``gettext``: ``_binary_ops``, ``_c2py_ops``
208+
* ``imaplib``: ``Commands``, ``Mon2num``
209+
* ``json.decoder``: ``_CONSTANTS``, ``BACKSLASH``
210+
* ``json.encoder``: ``ESCAPE_DCT``
211+
* ``json.tool``: ``_group_to_theme_color``
212+
* ``locale``: ``locale_encoding_alias``, ``locale_alias``,
213+
``windows_locale``
214+
* ``opcode``: ``_cache_format``, ``_inline_cache_entries``
215+
* ``optparse``: ``_builtin_cvt``
216+
* ``platform``: ``_ver_stages``, ``_default_architecture``
217+
* ``plistlib``: ``_BINARY_FORMAT``
218+
* ``ssl``: ``_PROTOCOL_NAMES``
219+
* ``stringprep``: ``b3_exceptions``
220+
* ``symtable``: ``_scopes_value_to_name``
221+
* ``tarfile``: ``PAX_NUMBER_FIELDS``, ``_NAMED_FILTERS``
222+
* ``token``: ``tok_name``, ``EXACT_TOKEN_TYPES``
223+
* ``tomllib._parser``: ``BASIC_STR_ESCAPE_REPLACEMENTS``
224+
* ``typing``: ``_PROTO_ALLOWLIST``
225+
226+
Extension modules
227+
-----------------
228+
229+
Replace ``dict`` with ``frozendict`` for constants:
230+
231+
* ``errno``: ``errorcode``
232+
233+
234+
Relationship to PEP 416 frozendict
235+
==================================
236+
237+
Since 2012 (:pep:`416`), the Python ecosystem has evolved:
238+
239+
* ``asyncio`` was added in 2014 (Python 3.4)
240+
* Free threading was added in 2024 (Python 3.13)
241+
* ``concurrent.interpreters`` was added in 2025 (Python 3.14)
242+
243+
There are now more use cases to share immutable mappings.
244+
245+
``frozendict`` now preserves the insertion order, whereas PEP 416
246+
``frozendict`` was unordered (as :pep:`603` ``frozenmap``). ``frozendict``
247+
relies on the ``dict`` implementation which preserves the insertion
248+
order since Python 3.6.
249+
250+
The first motivation to add ``frozendict`` was to implement a sandbox
251+
in Python. It's no longer the case in this PEP.
252+
253+
``types.MappingProxyType`` was added in 2012 (Python 3.3). This type is
254+
not hashable and it's not possible to inherit from it. It's also easy to
255+
retrieve the original dictionary which can be mutated, for example using
256+
``gc.get_referents()``.
257+
258+
259+
Relationship to PEP 603 frozenmap
260+
=================================
261+
262+
``collections.frozenmap`` has different properties than frozendict:
263+
264+
* ``frozenmap`` items are unordered, whereas ``frozendict`` preserves
265+
the insertion order.
266+
* ``frozenmap`` has additional methods:
267+
268+
* ``including(key, value)``
269+
* ``excluding(key)``
270+
* ``union(mapping=None, **kw)``
271+
272+
========== ============== ==============
273+
Complexity ``frozenmap`` ``frozendict``
274+
========== ============== ==============
275+
Lookup *O*\ (log *n*) *O*\ (1)
276+
Copy *O*\ (1) *O*\ (*n*)
277+
========== ============== ==============
278+
279+
280+
Reference Implementation
281+
========================
282+
283+
* https://github.com/python/cpython/pull/141508
284+
* ``frozendict`` shares most of its code with the ``dict`` type.
285+
* Add ``PyFrozenDictObject`` structure which inherits from
286+
``PyDictObject`` and has an additional ``ma_hash`` member.
287+
288+
289+
Thread Safety
290+
=============
291+
292+
Once the ``frozendict`` is created, it is immutable and can be shared
293+
safely between threads without any synchronization.
294+
295+
296+
Future Work
297+
===========
298+
299+
We are also going to make ``frozendict`` to be more efficient in terms
300+
of memory usage and performance compared to ``dict`` in future.
301+
302+
303+
Rejected Ideas
304+
==============
305+
306+
Inherit from dict
307+
-----------------
308+
309+
If ``frozendict`` inherits from ``dict``, it would become possible to
310+
call ``dict`` methods to mutate an immutable ``frozendict``. For
311+
example, it would be possible to call
312+
``dict.__setitem__(frozendict, key, value)``.
313+
314+
It may be possible to prevent modifying ``frozendict`` using ``dict``
315+
methods, but that would require to explicitly exclude ``frozendict``
316+
which can affect ``dict`` performance. Also, there is a higher risk of
317+
forgetting to exclude ``frozendict`` in some methods.
318+
319+
If ``frozendict`` does not inherit from ``dict``, there is no such
320+
issue.
321+
322+
323+
New syntax for ``frozendict`` literals
324+
--------------------------------------
325+
326+
Various syntaxes have been proposed to write ``frozendict`` literals.
327+
328+
A new syntax can be added later if needed.
329+
330+
331+
References
332+
==========
333+
334+
* :pep:`416` (``frozendict``)
335+
* :pep:`603` (``collections.frozenmap``)
336+
337+
338+
Acknowledgements
339+
================
340+
341+
This PEP is based on prior work from Yury Selivanov (:pep:`603`).
342+
343+
344+
Copyright
345+
=========
346+
347+
This document is placed in the public domain or under the
348+
CC0-1.0-Universal license, whichever is more permissive.

0 commit comments

Comments
 (0)