Skip to content

Commit 1637c04

Browse files
vstinnercorona10Marco-Sulla
committed
[WIP] PEP 814: Add built-in frozendict type
* Basic tests * Bare minimum documentation * Support frozendict in marshal, pickle, json * Replace dict with frozendict in many stdlib modules Co-Authored-by: Donghee Na <[email protected]> Co-Authored-by: Marco Sulla <[email protected]>
1 parent 2fbd396 commit 1637c04

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+935
-482
lines changed

Doc/library/stdtypes.rst

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4914,8 +4914,8 @@ The constructors for both classes work the same:
49144914

49154915
.. _typesmapping:
49164916

4917-
Mapping Types --- :class:`dict`
4918-
===============================
4917+
Mapping Types --- :class:`dict`, :class:`frozendict`
4918+
====================================================
49194919

49204920
.. index::
49214921
pair: object; mapping
@@ -4926,8 +4926,9 @@ Mapping Types --- :class:`dict`
49264926
pair: built-in function; len
49274927

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

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

5203+
.. class:: frozendict(**kwargs)
5204+
frozendict(mapping, /, **kwargs)
5205+
frozendict(iterable, /, **kwargs)
5206+
5207+
Return a new frozen dictionary initialized from an optional positional
5208+
argument and a possibly empty set of keyword arguments.
5209+
5210+
.. versionadded:: next
5211+
52025212

52035213
.. seealso::
52045214
:class:`types.MappingProxyType` can be used to create a read-only view
@@ -5532,6 +5542,7 @@ list is non-exhaustive.
55325542
* :class:`list`
55335543
* :class:`dict`
55345544
* :class:`set`
5545+
* :class:`frozendict`
55355546
* :class:`frozenset`
55365547
* :class:`type`
55375548
* :class:`asyncio.Future`

Include/cpython/dictobject.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ typedef struct {
3232
PyDictValues *ma_values;
3333
} PyDictObject;
3434

35+
// frozendict
36+
PyAPI_DATA(PyTypeObject) PyFrozenDict_Type;
37+
#define PyFrozenDict_Check(op) PyObject_TypeCheck((op), &PyFrozenDict_Type)
38+
#define PyFrozenDict_CheckExact(op) Py_IS_TYPE((op), &PyFrozenDict_Type)
39+
40+
#define _PyAnyDict_CheckExact(ob) \
41+
(PyDict_CheckExact(ob) || PyFrozenDict_CheckExact(ob))
42+
#define _PyAnyDict_Check(ob) \
43+
(PyDict_Check(ob) || PyFrozenDict_Check(ob))
44+
3545
PyAPI_FUNC(PyObject *) _PyDict_GetItem_KnownHash(PyObject *mp, PyObject *key,
3646
Py_hash_t hash);
3747
// PyDict_GetItemStringRef() can be used instead
@@ -52,7 +62,7 @@ PyAPI_FUNC(int) PyDict_SetDefaultRef(PyObject *mp, PyObject *key, PyObject *defa
5262
/* Get the number of items of a dictionary. */
5363
static inline Py_ssize_t PyDict_GET_SIZE(PyObject *op) {
5464
PyDictObject *mp;
55-
assert(PyDict_Check(op));
65+
assert(_PyAnyDict_Check(op));
5666
mp = _Py_CAST(PyDictObject*, op);
5767
#ifdef Py_GIL_DISABLED
5868
return _Py_atomic_load_ssize_relaxed(&mp->ma_used);
@@ -103,3 +113,6 @@ PyAPI_FUNC(int) PyDict_ClearWatcher(int watcher_id);
103113
// Mark given dictionary as "watched" (callback will be called if it is modified)
104114
PyAPI_FUNC(int) PyDict_Watch(int watcher_id, PyObject* dict);
105115
PyAPI_FUNC(int) PyDict_Unwatch(int watcher_id, PyObject* dict);
116+
117+
// Create a frozendict. Create an empty dictionary if iterable is NULL.
118+
PyAPI_FUNC(PyObject*) PyFrozenDict_New(PyObject *iterable);

Include/internal/pycore_dict.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,15 @@ _Py_DECREF_BUILTINS(PyObject *op)
406406
}
407407
#endif
408408

409+
/* frozendict */
410+
typedef struct {
411+
PyDictObject ob_base;
412+
Py_hash_t ma_hash;
413+
} PyFrozenDictObject;
414+
415+
#define _PyFrozenDictObject_CAST(op) \
416+
(assert(PyFrozenDict_Check(op)), _Py_CAST(PyFrozenDictObject*, (op)))
417+
409418
#ifdef __cplusplus
410419
}
411420
#endif

Include/internal/pycore_object.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -863,8 +863,7 @@ static inline Py_hash_t
863863
_PyObject_HashFast(PyObject *op)
864864
{
865865
if (PyUnicode_CheckExact(op)) {
866-
Py_hash_t hash = FT_ATOMIC_LOAD_SSIZE_RELAXED(
867-
_PyASCIIObject_CAST(op)->hash);
866+
Py_hash_t hash = PyUnstable_Unicode_GET_CACHED_HASH(op);
868867
if (hash != -1) {
869868
return hash;
870869
}

Lib/_collections_abc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,7 @@ def __eq__(self, other):
823823

824824
__reversed__ = None
825825

826+
Mapping.register(frozendict)
826827
Mapping.register(mappingproxy)
827828
Mapping.register(framelocalsproxy)
828829

Lib/_compat_pickle.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@
183183
'StringIO': 'io',
184184
'cStringIO': 'io',
185185
})
186+
IMPORT_MAPPING = frozendict(IMPORT_MAPPING)
186187

187188
REVERSE_IMPORT_MAPPING.update({
188189
'_bz2': 'bz2',
@@ -198,6 +199,7 @@
198199
('UserDict', 'UserDict'): ('collections', 'UserDict'),
199200
('socket', '_socketobject'): ('socket', 'SocketType'),
200201
})
202+
NAME_MAPPING = frozendict(NAME_MAPPING)
201203

202204
REVERSE_NAME_MAPPING.update({
203205
('_functools', 'reduce'): ('__builtin__', 'reduce'),

0 commit comments

Comments
 (0)