Skip to content

Commit 696cdfc

Browse files
vstinnerhugovkadamchainzbenediktjohannes
authored
gh-141510, PEP 814: Add built-in frozendict type (#144757)
Add TYPE_FROZENDICT to the marshal module. Add C API functions: * PyAnyDict_Check() * PyAnyDict_CheckExact() * PyFrozenDict_Check() * PyFrozenDict_CheckExact() * PyFrozenDict_New() Add PyFrozenDict_Type C type. Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: Adam Johnson <me@adamj.eu> Co-authored-by: Benedikt Johannes <benedikt.johannes.hofer@gmail.com>
1 parent 63531a3 commit 696cdfc

File tree

18 files changed

+579
-126
lines changed

18 files changed

+579
-126
lines changed

Doc/c-api/dict.rst

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
.. _dictobjects:
44

5-
Dictionary Objects
5+
Dictionary objects
66
------------------
77

88
.. index:: pair: object; dictionary
@@ -444,7 +444,7 @@ Dictionary Objects
444444
.. versionadded:: 3.12
445445
446446
447-
Dictionary View Objects
447+
Dictionary view objects
448448
^^^^^^^^^^^^^^^^^^^^^^^
449449
450450
.. c:function:: int PyDictViewSet_Check(PyObject *op)
@@ -490,7 +490,58 @@ Dictionary View Objects
490490
always succeeds.
491491
492492
493-
Ordered Dictionaries
493+
Frozen dictionary objects
494+
^^^^^^^^^^^^^^^^^^^^^^^^^
495+
496+
.. versionadded:: next
497+
498+
499+
.. c:var:: PyTypeObject PyFrozenDict_Type
500+
501+
This instance of :c:type:`PyTypeObject` represents the Python frozen
502+
dictionary type.
503+
This is the same object as :class:`frozendict` in the Python layer.
504+
505+
506+
.. c:function:: int PyAnyDict_Check(PyObject *p)
507+
508+
Return true if *p* is a :class:`dict` object, a :class:`frozendict` object,
509+
or an instance of a subtype of the :class:`!dict` or :class:`!frozendict`
510+
type.
511+
This function always succeeds.
512+
513+
514+
.. c:function:: int PyAnyDict_CheckExact(PyObject *p)
515+
516+
Return true if *p* is a :class:`dict` object or a :class:`frozendict` object,
517+
but not an instance of a subtype of the :class:`!dict` or
518+
:class:`!frozendict` type.
519+
This function always succeeds.
520+
521+
522+
.. c:function:: int PyFrozenDict_Check(PyObject *p)
523+
524+
Return true if *p* is a :class:`frozendict` object or an instance of a
525+
subtype of the :class:`!frozendict` type.
526+
This function always succeeds.
527+
528+
529+
.. c:function:: int PyFrozenDict_CheckExact(PyObject *p)
530+
531+
Return true if *p* is a :class:`frozendict` object, but not an instance of a
532+
subtype of the :class:`!frozendict` type.
533+
This function always succeeds.
534+
535+
536+
.. c:function:: PyObject* PyFrozenDict_New(PyObject *iterable)
537+
538+
Return a new :class:`frozendict` from an iterable, or ``NULL`` on failure
539+
with an exception set.
540+
541+
Create an empty dictionary if *iterable* is ``NULL``.
542+
543+
544+
Ordered dictionaries
494545
^^^^^^^^^^^^^^^^^^^^
495546
496547
Python's C API provides interface for :class:`collections.OrderedDict` from C.

Doc/library/stdtypes.rst

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5305,8 +5305,8 @@ frozenset, a temporary one is created from *elem*.
53055305

53065306
.. _typesmapping:
53075307

5308-
Mapping Types --- :class:`dict`
5309-
===============================
5308+
Mapping types --- :class:`!dict`, :class:`!frozendict`
5309+
======================================================
53105310

53115311
.. index::
53125312
pair: object; mapping
@@ -5317,8 +5317,9 @@ Mapping Types --- :class:`dict`
53175317
pair: built-in function; len
53185318

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

@@ -5588,10 +5589,9 @@ can be used interchangeably to index the same dictionary entry.
55885589
Dictionaries are now reversible.
55895590

55905591

5591-
.. seealso::
5592-
:class:`types.MappingProxyType` can be used to create a read-only view
5593-
of a :class:`dict`.
5594-
5592+
.. seealso::
5593+
:class:`types.MappingProxyType` can be used to create a read-only view
5594+
of a :class:`dict`.
55955595

55965596
.. _thread-safety-dict:
55975597

@@ -5839,6 +5839,41 @@ An example of dictionary view usage::
58395839
500
58405840

58415841

5842+
Frozen dictionaries
5843+
-------------------
5844+
5845+
.. class:: frozendict(**kwargs)
5846+
frozendict(mapping, /, **kwargs)
5847+
frozendict(iterable, /, **kwargs)
5848+
5849+
Return a new frozen dictionary initialized from an optional positional
5850+
argument and a possibly empty set of keyword arguments.
5851+
5852+
A :class:`!frozendict` has a similar API to the :class:`dict` API, with the
5853+
following differences:
5854+
5855+
* :class:`!dict` has more methods than :class:`!frozendict`:
5856+
5857+
* :meth:`!__delitem__`
5858+
* :meth:`!__setitem__`
5859+
* :meth:`~dict.clear`
5860+
* :meth:`~dict.pop`
5861+
* :meth:`~dict.popitem`
5862+
* :meth:`~dict.setdefault`
5863+
* :meth:`~dict.update`
5864+
5865+
* A :class:`!frozendict` can be hashed with ``hash(frozendict)`` if all keys and
5866+
values can be hashed.
5867+
5868+
* ``frozendict |= other`` does not modify the :class:`!frozendict` in-place but
5869+
creates a new frozen dictionary.
5870+
5871+
:class:`!frozendict` is not a :class:`!dict` subclass but inherits directly
5872+
from ``object``.
5873+
5874+
.. versionadded:: next
5875+
5876+
58425877
.. _typecontextmanager:
58435878

58445879
Context Manager Types
@@ -6062,6 +6097,7 @@ list is non-exhaustive.
60626097
* :class:`list`
60636098
* :class:`dict`
60646099
* :class:`set`
6100+
* :class:`frozendict`
60656101
* :class:`frozenset`
60666102
* :class:`type`
60676103
* :class:`asyncio.Future`

Doc/whatsnew/3.15.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ Summary -- Release highlights
6767
6868
* :pep:`810`: :ref:`Explicit lazy imports for faster startup times
6969
<whatsnew315-pep810>`
70+
* :pep:`814`: :ref:`Add frozendict built-in type
71+
<whatsnew315-frozendict>`
7072
* :pep:`799`: :ref:`A dedicated profiling package for organizing Python
7173
profiling tools <whatsnew315-profiling-package>`
7274
* :pep:`799`: :ref:`Tachyon: High frequency statistical sampling profiler
@@ -180,6 +182,21 @@ raise :exc:`SyntaxError`).
180182

181183
(Contributed by Pablo Galindo Salgado and Dino Viehland in :gh:`142349`.)
182184

185+
186+
.. _whatsnew315-frozendict:
187+
188+
:pep:`814`: Add frozendict built-in type
189+
----------------------------------------
190+
191+
A new public immutable type :class:`frozendict` is added to the :mod:`builtins`
192+
module. It is not a ``dict`` subclass but inherits directly from ``object``.
193+
194+
A ``frozendict`` can be hashed with ``hash(frozendict)`` if all keys and values
195+
can be hashed.
196+
197+
.. seealso:: :pep:`814` for the full specification and rationale.
198+
199+
183200
.. _whatsnew315-profiling-package:
184201

185202
:pep:`799`: A dedicated profiling package
@@ -1525,6 +1542,16 @@ C API changes
15251542
New features
15261543
------------
15271544

1545+
* Add the following functions for the new :class:`frozendict` type:
1546+
1547+
* :c:func:`PyAnyDict_Check`
1548+
* :c:func:`PyAnyDict_CheckExact`
1549+
* :c:func:`PyFrozenDict_Check`
1550+
* :c:func:`PyFrozenDict_CheckExact`
1551+
* :c:func:`PyFrozenDict_New`
1552+
1553+
(Contributed by Victor Stinner in :gh:`141510`.)
1554+
15281555
* Add :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`,
15291556
:c:func:`PySys_GetOptionalAttr`, and :c:func:`PySys_GetOptionalAttrString`
15301557
functions as replacements for :c:func:`PySys_GetObject`.

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
@@ -42,7 +52,7 @@ PyAPI_FUNC(PyObject *) PyDict_SetDefault(
4252
/* Get the number of items of a dictionary. */
4353
static inline Py_ssize_t PyDict_GET_SIZE(PyObject *op) {
4454
PyDictObject *mp;
45-
assert(PyDict_Check(op));
55+
assert(PyAnyDict_Check(op));
4656
mp = _Py_CAST(PyDictObject*, op);
4757
#ifdef Py_GIL_DISABLED
4858
return _Py_atomic_load_ssize_relaxed(&mp->ma_used);
@@ -93,3 +103,6 @@ PyAPI_FUNC(int) PyDict_ClearWatcher(int watcher_id);
93103
// Mark given dictionary as "watched" (callback will be called if it is modified)
94104
PyAPI_FUNC(int) PyDict_Watch(int watcher_id, PyObject* dict);
95105
PyAPI_FUNC(int) PyDict_Unwatch(int watcher_id, PyObject* dict);
106+
107+
// Create a frozendict. Create an empty dictionary if iterable is NULL.
108+
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
@@ -408,6 +408,15 @@ _Py_DECREF_BUILTINS(PyObject *op)
408408
}
409409
#endif
410410

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

Include/internal/pycore_typeobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern "C" {
2626
#define _Py_TYPE_VERSION_BYTEARRAY 9
2727
#define _Py_TYPE_VERSION_BYTES 10
2828
#define _Py_TYPE_VERSION_COMPLEX 11
29+
#define _Py_TYPE_VERSION_FROZENDICT 12
2930

3031
#define _Py_TYPE_VERSION_NEXT 16
3132

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

0 commit comments

Comments
 (0)