Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1637c04
[WIP] PEP 814: Add built-in frozendict type
vstinner Nov 2, 2025
49aca48
Update Tools/build/generate_token.py for token.py
vstinner Nov 13, 2025
3299299
Try to fix build on Ubuntu and macOS
vstinner Nov 13, 2025
18ce520
Fix make check-c-globals
vstinner Nov 13, 2025
1a72eec
Add PyFrozenDict_Type to static_types
vstinner Nov 13, 2025
9d6ca58
Update Doc/library/stdtypes.rst
vstinner Nov 13, 2025
439c98c
Replace PyDict_Check() with _PyAnyDict_Check()
vstinner Nov 13, 2025
4a1a504
copy: support frozendict
vstinner Nov 13, 2025
302767b
Fix frozendict.__reduce_ex__()
vstinner Nov 13, 2025
07b3510
exec(): accept frozendict for globals
vstinner Nov 13, 2025
04f66ad
Make PyAnyDict_Check() public
vstinner Nov 13, 2025
6fca6d1
Optimize frozendict.copy()
vstinner Nov 14, 2025
6727967
Fix frozendict merge ("|=" operator); add tests
vstinner Nov 14, 2025
d231cdb
copy: use _copy_atomic_types instead; add tests
vstinner Nov 14, 2025
3a07c46
frozendict
vstinner Nov 14, 2025
bc25bb2
pprint supports frozendict
vstinner Nov 14, 2025
0acff5e
change json.tool._group_to_theme_color formatting
vstinner Nov 14, 2025
e90156e
json: use PyAnyDict_Check()
vstinner Nov 14, 2025
fe7e7f5
_pickle: fix refleak
vstinner Nov 14, 2025
4c5c8d2
frozendict_hash: use atomic load/store
vstinner Nov 14, 2025
0a57448
Update Lib/pydoc_data/topics.py
vstinner Nov 14, 2025
fc66056
Fix test_copy: remove duplicated test
vstinner Nov 17, 2025
c1f5055
frozendict | {} returns the same frozendict
vstinner Dec 12, 2025
6ff9c67
Fix frozendict_or()
vstinner Dec 12, 2025
9a64439
Moar frozendict|frozendict optimizations
vstinner Dec 12, 2025
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
19 changes: 15 additions & 4 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4914,8 +4914,8 @@ The constructors for both classes work the same:

.. _typesmapping:

Mapping Types --- :class:`dict`
===============================
Mapping Types --- :class:`dict`, :class:`frozendict`
====================================================

.. index::
pair: object; mapping
Expand All @@ -4926,8 +4926,9 @@ Mapping Types --- :class:`dict`
pair: built-in function; len

A :term:`mapping` object maps :term:`hashable` values to arbitrary objects.
Mappings are mutable objects. There is currently only one standard mapping
type, the :dfn:`dictionary`. (For other containers see the built-in
There are currently two standard mapping types, the :dfn:`dictionary` and
:class:`frozendict`.
(For other containers see the built-in
:class:`list`, :class:`set`, and :class:`tuple` classes, and the
:mod:`collections` module.)

Expand Down Expand Up @@ -5199,6 +5200,15 @@ can be used interchangeably to index the same dictionary entry.
.. versionchanged:: 3.8
Dictionaries are now reversible.

.. class:: frozendict(**kwargs)
frozendict(mapping, /, **kwargs)
frozendict(iterable, /, **kwargs)

Return a new frozen dictionary initialized from an optional positional
argument and a possibly empty set of keyword arguments.

.. versionadded:: next


.. seealso::
:class:`types.MappingProxyType` can be used to create a read-only view
Expand Down Expand Up @@ -5532,6 +5542,7 @@ list is non-exhaustive.
* :class:`list`
* :class:`dict`
* :class:`set`
* :class:`frozendict`
* :class:`frozenset`
* :class:`type`
* :class:`asyncio.Future`
Expand Down
15 changes: 14 additions & 1 deletion Include/cpython/dictobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ typedef struct {
PyDictValues *ma_values;
} PyDictObject;

// frozendict
PyAPI_DATA(PyTypeObject) PyFrozenDict_Type;
#define PyFrozenDict_Check(op) PyObject_TypeCheck((op), &PyFrozenDict_Type)
#define PyFrozenDict_CheckExact(op) Py_IS_TYPE((op), &PyFrozenDict_Type)

#define PyAnyDict_CheckExact(ob) \
(PyDict_CheckExact(ob) || PyFrozenDict_CheckExact(ob))
#define PyAnyDict_Check(ob) \
(PyDict_Check(ob) || PyFrozenDict_Check(ob))

PyAPI_FUNC(PyObject *) _PyDict_GetItem_KnownHash(PyObject *mp, PyObject *key,
Py_hash_t hash);
// PyDict_GetItemStringRef() can be used instead
Expand All @@ -52,7 +62,7 @@ PyAPI_FUNC(int) PyDict_SetDefaultRef(PyObject *mp, PyObject *key, PyObject *defa
/* Get the number of items of a dictionary. */
static inline Py_ssize_t PyDict_GET_SIZE(PyObject *op) {
PyDictObject *mp;
assert(PyDict_Check(op));
assert(PyAnyDict_Check(op));
mp = _Py_CAST(PyDictObject*, op);
#ifdef Py_GIL_DISABLED
return _Py_atomic_load_ssize_relaxed(&mp->ma_used);
Expand Down Expand Up @@ -103,3 +113,6 @@ PyAPI_FUNC(int) PyDict_ClearWatcher(int watcher_id);
// Mark given dictionary as "watched" (callback will be called if it is modified)
PyAPI_FUNC(int) PyDict_Watch(int watcher_id, PyObject* dict);
PyAPI_FUNC(int) PyDict_Unwatch(int watcher_id, PyObject* dict);

// Create a frozendict. Create an empty dictionary if iterable is NULL.
PyAPI_FUNC(PyObject*) PyFrozenDict_New(PyObject *iterable);
9 changes: 9 additions & 0 deletions Include/internal/pycore_dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,15 @@ _Py_DECREF_BUILTINS(PyObject *op)
}
#endif

/* frozendict */
typedef struct {
PyDictObject ob_base;
Py_hash_t ma_hash;
} PyFrozenDictObject;

#define _PyFrozenDictObject_CAST(op) \
(assert(PyFrozenDict_Check(op)), _Py_CAST(PyFrozenDictObject*, (op)))

#ifdef __cplusplus
}
#endif
Expand Down
3 changes: 1 addition & 2 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -863,8 +863,7 @@ static inline Py_hash_t
_PyObject_HashFast(PyObject *op)
{
if (PyUnicode_CheckExact(op)) {
Py_hash_t hash = FT_ATOMIC_LOAD_SSIZE_RELAXED(
_PyASCIIObject_CAST(op)->hash);
Py_hash_t hash = PyUnstable_Unicode_GET_CACHED_HASH(op);
if (hash != -1) {
return hash;
}
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ extern "C" {
#define _Py_TYPE_VERSION_BYTEARRAY 9
#define _Py_TYPE_VERSION_BYTES 10
#define _Py_TYPE_VERSION_COMPLEX 11
#define _Py_TYPE_VERSION_FROZENDICT 12

#define _Py_TYPE_VERSION_NEXT 16

Expand Down
1 change: 1 addition & 0 deletions Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,7 @@ def __eq__(self, other):

__reversed__ = None

Mapping.register(frozendict)
Mapping.register(mappingproxy)
Mapping.register(framelocalsproxy)

Expand Down
2 changes: 2 additions & 0 deletions Lib/_compat_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
'StringIO': 'io',
'cStringIO': 'io',
})
IMPORT_MAPPING = frozendict(IMPORT_MAPPING)

REVERSE_IMPORT_MAPPING.update({
'_bz2': 'bz2',
Expand All @@ -198,6 +199,7 @@
('UserDict', 'UserDict'): ('collections', 'UserDict'),
('socket', '_socketobject'): ('socket', 'SocketType'),
})
NAME_MAPPING = frozendict(NAME_MAPPING)

REVERSE_NAME_MAPPING.update({
('_functools', 'reduce'): ('__builtin__', 'reduce'),
Expand Down
Loading