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>
.. _dictobjects:
-Dictionary Objects
+Dictionary objects
------------------
.. index:: pair: object; dictionary
.. versionadded:: 3.12
-Dictionary View Objects
+Dictionary view objects
^^^^^^^^^^^^^^^^^^^^^^^
.. c:function:: int PyDictViewSet_Check(PyObject *op)
always succeeds.
-Ordered Dictionaries
+Frozen dictionary objects
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: next
+
+
+.. c:var:: PyTypeObject PyFrozenDict_Type
+
+ This instance of :c:type:`PyTypeObject` represents the Python frozen
+ dictionary type.
+ This is the same object as :class:`frozendict` in the Python layer.
+
+
+.. c:function:: int PyAnyDict_Check(PyObject *p)
+
+ Return true if *p* is a :class:`dict` object, a :class:`frozendict` object,
+ or an instance of a subtype of the :class:`!dict` or :class:`!frozendict`
+ type.
+ This function always succeeds.
+
+
+.. c:function:: int PyAnyDict_CheckExact(PyObject *p)
+
+ Return true if *p* is a :class:`dict` object or a :class:`frozendict` object,
+ but not an instance of a subtype of the :class:`!dict` or
+ :class:`!frozendict` type.
+ This function always succeeds.
+
+
+.. c:function:: int PyFrozenDict_Check(PyObject *p)
+
+ Return true if *p* is a :class:`frozendict` object or an instance of a
+ subtype of the :class:`!frozendict` type.
+ This function always succeeds.
+
+
+.. c:function:: int PyFrozenDict_CheckExact(PyObject *p)
+
+ Return true if *p* is a :class:`frozendict` object, but not an instance of a
+ subtype of the :class:`!frozendict` type.
+ This function always succeeds.
+
+
+.. c:function:: PyObject* PyFrozenDict_New(PyObject *iterable)
+
+ Return a new :class:`frozendict` from an iterable, or ``NULL`` on failure
+ with an exception set.
+
+ Create an empty dictionary if *iterable* is ``NULL``.
+
+
+Ordered dictionaries
^^^^^^^^^^^^^^^^^^^^
Python's C API provides interface for :class:`collections.OrderedDict` from C.
.. _typesmapping:
-Mapping Types --- :class:`dict`
-===============================
+Mapping types --- :class:`!dict`, :class:`!frozendict`
+======================================================
.. index::
pair: object; mapping
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.)
Dictionaries are now reversible.
-.. seealso::
- :class:`types.MappingProxyType` can be used to create a read-only view
- of a :class:`dict`.
-
+ .. seealso::
+ :class:`types.MappingProxyType` can be used to create a read-only view
+ of a :class:`dict`.
.. _thread-safety-dict:
500
+Frozen dictionaries
+-------------------
+
+.. 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.
+
+ A :class:`!frozendict` has a similar API to the :class:`dict` API, with the
+ following differences:
+
+ * :class:`!dict` has more methods than :class:`!frozendict`:
+
+ * :meth:`!__delitem__`
+ * :meth:`!__setitem__`
+ * :meth:`~dict.clear`
+ * :meth:`~dict.pop`
+ * :meth:`~dict.popitem`
+ * :meth:`~dict.setdefault`
+ * :meth:`~dict.update`
+
+ * A :class:`!frozendict` can be hashed with ``hash(frozendict)`` if all keys and
+ values can be hashed.
+
+ * ``frozendict |= other`` does not modify the :class:`!frozendict` in-place but
+ creates a new frozen dictionary.
+
+ :class:`!frozendict` is not a :class:`!dict` subclass but inherits directly
+ from ``object``.
+
+ .. versionadded:: next
+
+
.. _typecontextmanager:
Context Manager Types
* :class:`list`
* :class:`dict`
* :class:`set`
+* :class:`frozendict`
* :class:`frozenset`
* :class:`type`
* :class:`asyncio.Future`
* :pep:`810`: :ref:`Explicit lazy imports for faster startup times
<whatsnew315-pep810>`
+* :pep:`814`: :ref:`Add frozendict built-in type
+ <whatsnew315-frozendict>`
* :pep:`799`: :ref:`A dedicated profiling package for organizing Python
profiling tools <whatsnew315-profiling-package>`
* :pep:`799`: :ref:`Tachyon: High frequency statistical sampling profiler
(Contributed by Pablo Galindo Salgado and Dino Viehland in :gh:`142349`.)
+
+.. _whatsnew315-frozendict:
+
+:pep:`814`: Add frozendict built-in type
+----------------------------------------
+
+A new public immutable type :class:`frozendict` is added to the :mod:`builtins`
+module. It is not a ``dict`` subclass but inherits directly from ``object``.
+
+A ``frozendict`` can be hashed with ``hash(frozendict)`` if all keys and values
+can be hashed.
+
+.. seealso:: :pep:`814` for the full specification and rationale.
+
+
.. _whatsnew315-profiling-package:
:pep:`799`: A dedicated profiling package
New features
------------
+* Add the following functions for the new :class:`frozendict` type:
+
+ * :c:func:`PyAnyDict_Check`
+ * :c:func:`PyAnyDict_CheckExact`
+ * :c:func:`PyFrozenDict_Check`
+ * :c:func:`PyFrozenDict_CheckExact`
+ * :c:func:`PyFrozenDict_New`
+
+ (Contributed by Victor Stinner in :gh:`141510`.)
+
* Add :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`,
:c:func:`PySys_GetOptionalAttr`, and :c:func:`PySys_GetOptionalAttrString`
functions as replacements for :c:func:`PySys_GetObject`.
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
/* 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);
// 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);
}
#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
#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
__reversed__ = None
+Mapping.register(frozendict)
Mapping.register(mappingproxy)
Mapping.register(framelocalsproxy)
from test import support
-class BasicTestMappingProtocol(unittest.TestCase):
+class BasicTestImmutableMappingProtocol(unittest.TestCase):
# This base class can be used to check that an object conforms to the
# mapping protocol
"""Return an empty mapping object"""
return self.type2test()
def _full_mapping(self, data):
- """Return a mapping object with the value contained in data
+ """Return a mapping object with the values contained in data
dictionary"""
- x = self._empty_mapping()
- for key, value in data.items():
- x[key] = value
- return x
+ return self.type2test(data)
def __init__(self, *args, **kw):
unittest.TestCase.__init__(self, *args, **kw)
self.assertEqual(d.get(knownkey, knownvalue), knownvalue)
self.assertNotIn(knownkey, d)
+ def test_constructor(self):
+ self.assertEqual(self._empty_mapping(), self._empty_mapping())
+
+ def test_bool(self):
+ self.assertTrue(not self._empty_mapping())
+ self.assertTrue(self.reference)
+ self.assertFalse(bool(self._empty_mapping()))
+ self.assertTrue(bool(self.reference))
+
+ def test_keys(self):
+ d = self._empty_mapping()
+ self.assertEqual(list(d.keys()), [])
+ d = self.reference
+ self.assertIn(list(self.inmapping.keys())[0], d.keys())
+ self.assertNotIn(list(self.other.keys())[0], d.keys())
+ self.assertRaises(TypeError, d.keys, None)
+
+ def test_values(self):
+ d = self._empty_mapping()
+ self.assertEqual(list(d.values()), [])
+
+ self.assertRaises(TypeError, d.values, None)
+
+ def test_items(self):
+ d = self._empty_mapping()
+ self.assertEqual(list(d.items()), [])
+
+ self.assertRaises(TypeError, d.items, None)
+
+ def test_len(self):
+ d = self._empty_mapping()
+ self.assertEqual(len(d), 0)
+
+ def test_getitem(self):
+ d = self.reference
+ self.assertEqual(d[list(self.inmapping.keys())[0]],
+ list(self.inmapping.values())[0])
+
+ self.assertRaises(TypeError, d.__getitem__)
+
+ # no test_fromkeys or test_copy as both os.environ and selves don't support it
+
+ def test_get(self):
+ d = self._empty_mapping()
+ self.assertIsNone(d.get(list(self.other.keys())[0]))
+ self.assertEqual(d.get(list(self.other.keys())[0], 3), 3)
+ d = self.reference
+ self.assertIsNone(d.get(list(self.other.keys())[0]))
+ self.assertEqual(d.get(list(self.other.keys())[0], 3), 3)
+ self.assertEqual(d.get(list(self.inmapping.keys())[0]),
+ list(self.inmapping.values())[0])
+ self.assertEqual(d.get(list(self.inmapping.keys())[0], 3),
+ list(self.inmapping.values())[0])
+ self.assertRaises(TypeError, d.get)
+ self.assertRaises(TypeError, d.get, None, None, None)
+
+
+class BasicTestMappingProtocol(BasicTestImmutableMappingProtocol):
+ def _full_mapping(self, data):
+ """Return a mapping object with the values contained in data
+ dictionary"""
+ x = self._empty_mapping()
+ for key, value in data.items():
+ x[key] = value
+ return x
+
def test_write(self):
# Test for write operations on mapping
p = self._empty_mapping()
p=self._empty_mapping()
self.assertRaises(KeyError, p.popitem)
- def test_constructor(self):
- self.assertEqual(self._empty_mapping(), self._empty_mapping())
-
- def test_bool(self):
- self.assertTrue(not self._empty_mapping())
- self.assertTrue(self.reference)
- self.assertTrue(bool(self._empty_mapping()) is False)
- self.assertTrue(bool(self.reference) is True)
-
- def test_keys(self):
- d = self._empty_mapping()
- self.assertEqual(list(d.keys()), [])
- d = self.reference
- self.assertIn(list(self.inmapping.keys())[0], d.keys())
- self.assertNotIn(list(self.other.keys())[0], d.keys())
- self.assertRaises(TypeError, d.keys, None)
-
- def test_values(self):
- d = self._empty_mapping()
- self.assertEqual(list(d.values()), [])
-
- self.assertRaises(TypeError, d.values, None)
-
- def test_items(self):
- d = self._empty_mapping()
- self.assertEqual(list(d.items()), [])
-
- self.assertRaises(TypeError, d.items, None)
-
- def test_len(self):
- d = self._empty_mapping()
- self.assertEqual(len(d), 0)
-
- def test_getitem(self):
- d = self.reference
- self.assertEqual(d[list(self.inmapping.keys())[0]],
- list(self.inmapping.values())[0])
-
- self.assertRaises(TypeError, d.__getitem__)
-
def test_update(self):
# mapping argument
d = self._empty_mapping()
self.assertRaises(ValueError, d.update, [(1, 2, 3)])
- # no test_fromkeys or test_copy as both os.environ and selves don't support it
-
- def test_get(self):
- d = self._empty_mapping()
- self.assertTrue(d.get(list(self.other.keys())[0]) is None)
- self.assertEqual(d.get(list(self.other.keys())[0], 3), 3)
- d = self.reference
- self.assertTrue(d.get(list(self.other.keys())[0]) is None)
- self.assertEqual(d.get(list(self.other.keys())[0], 3), 3)
- self.assertEqual(d.get(list(self.inmapping.keys())[0]),
- list(self.inmapping.values())[0])
- self.assertEqual(d.get(list(self.inmapping.keys())[0], 3),
- list(self.inmapping.values())[0])
- self.assertRaises(TypeError, d.get)
- self.assertRaises(TypeError, d.get, None, None, None)
-
def test_setdefault(self):
d = self._empty_mapping()
self.assertRaises(TypeError, d.setdefault)
class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol):
type2test = Dict
+class FrozenDictMappingTests(mapping_tests.BasicTestImmutableMappingProtocol):
+ type2test = frozendict
+
+
+class FrozenDict(frozendict):
+ pass
+
+
+class FrozenDictTests(unittest.TestCase):
+ def test_copy(self):
+ d = frozendict(x=1, y=2)
+ d2 = d.copy()
+ self.assertIs(d2, d)
+
+ d = FrozenDict(x=1, y=2)
+ d2 = d.copy()
+ self.assertIsNot(d2, d)
+ self.assertEqual(d2, frozendict(x=1, y=2))
+ self.assertEqual(type(d2), frozendict)
+
+ def test_merge(self):
+ # test "a | b" operator
+ self.assertEqual(frozendict(x=1) | frozendict(y=2),
+ frozendict({'x': 1, 'y': 2}))
+ self.assertEqual(frozendict(x=1) | dict(y=2),
+ frozendict({'x': 1, 'y': 2}))
+ self.assertEqual(frozendict(x=1, y=2) | frozendict(y=5),
+ frozendict({'x': 1, 'y': 5}))
+ fd = frozendict(x=1, y=2)
+ self.assertIs(fd | frozendict(), fd)
+ self.assertIs(fd | {}, fd)
+ self.assertIs(frozendict() | fd, fd)
+
+ def test_update(self):
+ # test "a |= b" operator
+ d = frozendict(x=1)
+ copy = d
+ self.assertIs(copy, d)
+ d |= frozendict(y=2)
+ self.assertIsNot(copy, d)
+ self.assertEqual(d, frozendict({'x': 1, 'y': 2}))
+ self.assertEqual(copy, frozendict({'x': 1}))
+
+ def test_repr(self):
+ d = frozendict(x=1, y=2)
+ self.assertEqual(repr(d), "frozendict({'x': 1, 'y': 2})")
+
+ class MyFrozenDict(frozendict):
+ pass
+ d = MyFrozenDict(x=1, y=2)
+ self.assertEqual(repr(d), "MyFrozenDict({'x': 1, 'y': 2})")
+
if __name__ == "__main__":
unittest.main()
>>> import builtins
>>> tests = doctest.DocTestFinder().find(builtins)
- >>> 750 < len(tests) < 800 # approximate number of objects with docstrings
+ >>> 750 < len(tests) < 850 # approximate number of objects with docstrings
True
>>> real_tests = [t for t in tests if len(t.examples) > 0]
>>> len(real_tests) # objects that actually have doctests
self.assertRaises(ValueError, inspect.signature, getattr(cls, name))
def test_builtins_have_signatures(self):
- no_signature = {'type', 'super', 'bytearray', 'bytes', 'dict', 'int', 'str'}
+ no_signature = {'type', 'super', 'bytearray', 'bytes',
+ 'dict', 'frozendict', 'int', 'str'}
# These need PEP 457 groups
needs_groups = {"range", "slice", "dir", "getattr",
"next", "iter", "vars"}
--- /dev/null
+Add the following functions for the new :class:`frozendict` type:
+
+* :c:func:`PyAnyDict_Check`
+* :c:func:`PyAnyDict_CheckExact`
+* :c:func:`PyFrozenDict_Check`
+* :c:func:`PyFrozenDict_CheckExact`
+* :c:func:`PyFrozenDict_New`
+
+Patch by Victor Stinner.
--- /dev/null
+Add built-in :class:`frozendict` type. Patch by Victor Stinner.
#include "stringlib/eq.h" // unicode_eq()
#include <stdbool.h>
+// Forward declarations
+static PyObject* frozendict_new(PyTypeObject *type, PyObject *args,
+ PyObject *kwds);
+
/*[clinic input]
class dict "PyDictObject *" "&PyDict_Type"
#endif
+#define _PyAnyDict_CAST(op) \
+ (assert(PyAnyDict_Check(op)), _Py_CAST(PyDictObject*, op))
+
+#define GET_USED(ep) FT_ATOMIC_LOAD_SSIZE_RELAXED((ep)->ma_used)
+
#define STORE_KEY(ep, key) FT_ATOMIC_STORE_PTR_RELEASE((ep)->me_key, key)
#define STORE_VALUE(ep, value) FT_ATOMIC_STORE_PTR_RELEASE((ep)->me_value, value)
#define STORE_SPLIT_VALUE(mp, idx, value) FT_ATOMIC_STORE_PTR_RELEASE(mp->ma_values->values[idx], value)
do { if (!(expr)) { _PyObject_ASSERT_FAILED_MSG(op, Py_STRINGIFY(expr)); } } while (0)
assert(op != NULL);
- CHECK(PyDict_Check(op));
+ CHECK(PyAnyDict_Check(op));
PyDictObject *mp = (PyDictObject *)op;
PyDictKeysObject *keys = mp->ma_keys;
static PyDictKeysObject *
clone_combined_dict_keys(PyDictObject *orig)
{
- assert(PyDict_Check(orig));
+ assert(PyAnyDict_Check(orig));
assert(Py_TYPE(orig)->tp_iter == dict_iter);
assert(orig->ma_values == NULL);
assert(orig->ma_keys != Py_EMPTY_KEYS);
static PyObject *
dict_getitem(PyObject *op, PyObject *key, const char *warnmsg)
{
- if (!PyDict_Check(op)) {
+ if (!PyAnyDict_Check(op)) {
return NULL;
}
PyDictObject *mp = (PyDictObject *)op;
PyDictObject *mp = (PyDictObject *)op;
PyObject *value;
- if (!PyDict_Check(op)) {
+ if (!PyAnyDict_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
int
PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result)
{
- if (!PyDict_Check(op)) {
+ if (!PyAnyDict_Check(op)) {
PyErr_BadInternalCall();
*result = NULL;
return -1;
PyDictObject*mp = (PyDictObject *)op;
PyObject *value;
- if (!PyDict_Check(op)) {
+ if (!PyAnyDict_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
assert(key);
assert(value);
- assert(PyDict_Check(mp));
+ assert(PyAnyDict_Check(mp));
Py_hash_t hash = _PyObject_HashFast(key);
if (hash == -1) {
dict_unhashable_type(key);
Py_NewRef(key), Py_NewRef(value));
}
+static int
+_PyAnyDict_SetItem(PyObject *op, PyObject *key, PyObject *value)
+{
+ assert(PyAnyDict_Check(op));
+ assert(key);
+ assert(value);
+ return _PyDict_SetItem_Take2((PyDictObject *)op,
+ Py_NewRef(key), Py_NewRef(value));
+}
+
static int
setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
{
PyObject *key, *value;
Py_hash_t hash;
- if (!PyDict_Check(op))
+ if (!PyAnyDict_Check(op))
return 0;
mp = (PyDictObject *)op;
return NULL;
- if (PyDict_CheckExact(d)) {
- if (PyDict_CheckExact(iterable)) {
+ if (PyAnyDict_CheckExact(d)) {
+ if (PyAnyDict_CheckExact(iterable)) {
PyDictObject *mp = (PyDictObject *)d;
Py_BEGIN_CRITICAL_SECTION2(d, iterable);
return NULL;
}
- if (PyDict_CheckExact(d)) {
+ if (PyAnyDict_CheckExact(d)) {
Py_BEGIN_CRITICAL_SECTION(d);
while ((key = PyIter_Next(it)) != NULL) {
status = setitem_lock_held((PyDictObject *)d, key, value);
static Py_ssize_t
dict_length(PyObject *self)
{
- return FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyDictObject *)self)->ma_used);
+ return GET_USED(_PyAnyDict_CAST(self));
}
static PyObject *
if (ix == DKIX_ERROR)
return NULL;
if (ix == DKIX_EMPTY || value == NULL) {
- if (!PyDict_CheckExact(mp)) {
+ if (!PyAnyDict_CheckExact(mp)) {
/* Look up __missing__ method if we're a subclass. */
PyObject *missing, *res;
missing = _PyObject_LookupSpecial(
{
ASSERT_DICT_LOCKED(dict);
- if (dict == NULL || !PyDict_Check(dict)) {
+ if (dict == NULL || !PyAnyDict_Check(dict)) {
PyErr_BadInternalCall();
return NULL;
}
{
ASSERT_DICT_LOCKED(dict);
- if (dict == NULL || !PyDict_Check(dict)) {
+ if (dict == NULL || !PyAnyDict_Check(dict)) {
PyErr_BadInternalCall();
return NULL;
}
{
ASSERT_DICT_LOCKED(dict);
- if (dict == NULL || !PyDict_Check(dict)) {
+ if (dict == NULL || !PyAnyDict_Check(dict)) {
PyErr_BadInternalCall();
return NULL;
}
static int
dict_update_arg(PyObject *self, PyObject *arg)
{
- if (PyDict_CheckExact(arg)) {
+ if (PyAnyDict_CheckExact(arg)) {
return PyDict_Merge(self, arg, 1);
}
int has_keys = PyObject_HasAttrWithError(arg, &_Py_ID(keys));
PyObject *fast; /* item as a 2-tuple or 2-list */
assert(d != NULL);
- assert(PyDict_Check(d));
+ assert(PyAnyDict_Check(d));
assert(seq2 != NULL);
it = PyObject_GetIter(seq2);
* things quite efficiently. For the latter, we only require that
* PyMapping_Keys() and PyObject_GetItem() be supported.
*/
- if (a == NULL || !PyDict_Check(a) || b == NULL) {
+ if (a == NULL || !PyAnyDict_Check(a) || b == NULL) {
PyErr_BadInternalCall();
return -1;
}
mp = (PyDictObject*)a;
int res = 0;
- if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) {
+ if (PyAnyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) {
other = (PyDictObject*)b;
int res;
Py_BEGIN_CRITICAL_SECTION2(a, b);
dict_copy_impl(PyDictObject *self)
/*[clinic end generated code: output=ffb782cf970a5c39 input=73935f042b639de4]*/
{
+ if (PyFrozenDict_CheckExact(self)) {
+ return Py_NewRef(self);
+ }
return PyDict_Copy((PyObject *)self);
}
{
PyObject *copy;
PyDictObject *mp;
+ int frozendict = PyFrozenDict_Check(o);
ASSERT_DICT_LOCKED(o);
mp = (PyDictObject *)o;
if (mp->ma_used == 0) {
/* The dict is empty; just return a new dict. */
- return PyDict_New();
+ if (frozendict) {
+ return PyFrozenDict_New(NULL);
+ }
+ else {
+ return PyDict_New();
+ }
}
if (_PyDict_HasSplitTable(mp)) {
if (newvalues == NULL) {
return PyErr_NoMemory();
}
- split_copy = PyObject_GC_New(PyDictObject, &PyDict_Type);
+ if (frozendict) {
+ split_copy = (PyDictObject *)PyObject_GC_New(PyFrozenDictObject,
+ &PyFrozenDict_Type);
+ }
+ else {
+ split_copy = PyObject_GC_New(PyDictObject, &PyDict_Type);
+ }
if (split_copy == NULL) {
free_values(newvalues, false);
return NULL;
split_copy->ma_used = mp->ma_used;
split_copy->_ma_watcher_tag = 0;
dictkeys_incref(mp->ma_keys);
+ if (frozendict) {
+ PyFrozenDictObject *frozen = (PyFrozenDictObject *)split_copy;
+ frozen->ma_hash = -1;
+ }
_PyObject_GC_TRACK(split_copy);
return (PyObject *)split_copy;
}
if (Py_TYPE(mp)->tp_iter == dict_iter &&
mp->ma_values == NULL &&
- (mp->ma_used >= (mp->ma_keys->dk_nentries * 2) / 3))
+ (mp->ma_used >= (mp->ma_keys->dk_nentries * 2) / 3) &&
+ !frozendict)
{
/* Use fast-copy if:
return (PyObject *)new;
}
- copy = PyDict_New();
+ if (frozendict) {
+ copy = PyFrozenDict_New(NULL);
+ }
+ else {
+ copy = PyDict_New();
+ }
if (copy == NULL)
return NULL;
if (dict_merge(copy, o, 1) == 0)
PyObject *
PyDict_Copy(PyObject *o)
{
- if (o == NULL || !PyDict_Check(o)) {
+ if (o == NULL || !PyAnyDict_Check(o)) {
PyErr_BadInternalCall();
return NULL;
}
Py_ssize_t
PyDict_Size(PyObject *mp)
{
- if (mp == NULL || !PyDict_Check(mp)) {
+ if (mp == NULL || !PyAnyDict_Check(mp)) {
PyErr_BadInternalCall();
return -1;
}
- return FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyDictObject *)mp)->ma_used);
+ return GET_USED((PyDictObject *)mp);
}
/* Return 1 if dicts equal, 0 if not, -1 if error.
int cmp;
PyObject *res;
- if (!PyDict_Check(v) || !PyDict_Check(w)) {
+ if (!PyAnyDict_Check(v) || !PyAnyDict_Check(w)) {
res = Py_NotImplemented;
}
else if (op == Py_EQ || op == Py_NE) {
static PyObject *
dict_or(PyObject *self, PyObject *other)
{
- if (!PyDict_Check(self) || !PyDict_Check(other)) {
+ if (!PyAnyDict_Check(self) || !PyAnyDict_Check(other)) {
Py_RETURN_NOTIMPLEMENTED;
}
PyObject *new = PyDict_Copy(self);
return new;
}
+static PyObject *
+frozendict_or(PyObject *self, PyObject *other)
+{
+ if (PyFrozenDict_CheckExact(self)) {
+ // frozendict() | frozendict(...) => frozendict(...)
+ if (GET_USED((PyDictObject *)self) == 0
+ && PyFrozenDict_CheckExact(other))
+ {
+ return Py_NewRef(other);
+ }
+
+ // frozendict(...) | frozendict() => frozendict(...)
+ if (PyAnyDict_CheckExact(other)
+ && GET_USED((PyDictObject *)other) == 0)
+ {
+ return Py_NewRef(self);
+ }
+ }
+
+ return dict_or(self, other);
+}
+
+
static PyObject *
dict_ior(PyObject *self, PyObject *other)
{
return NULL;
}
- PyObject *self = dict_new(_PyType_CAST(type), NULL, NULL);
+ PyObject *self;
+ if (Py_Is((PyTypeObject*)type, &PyFrozenDict_Type)
+ || PyType_IsSubtype((PyTypeObject*)type, &PyFrozenDict_Type))
+ {
+ self = frozendict_new(_PyType_CAST(type), NULL, NULL);
+ }
+ else {
+ self = dict_new(_PyType_CAST(type), NULL, NULL);
+ }
if (self == NULL) {
return NULL;
}
}
if (kwnames != NULL) {
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwnames); i++) {
- if (PyDict_SetItem(self, PyTuple_GET_ITEM(kwnames, i), args[i]) < 0) {
+ PyObject *key = PyTuple_GET_ITEM(kwnames, i); // borrowed
+ if (_PyAnyDict_SetItem(self, key, args[i]) < 0) {
Py_DECREF(self);
return NULL;
}
.tp_version_tag = _Py_TYPE_VERSION_DICT,
};
+
/* For backward compatibility with old dictionary interface */
PyObject *
return NULL;
}
di->di_dict = (PyDictObject*)Py_NewRef(dict);
- used = FT_ATOMIC_LOAD_SSIZE_RELAXED(dict->ma_used);
+ used = GET_USED(dict);
di->di_used = used;
di->len = used;
if (itertype == &PyDictRevIterKey_Type ||
{
dictiterobject *di = (dictiterobject *)self;
Py_ssize_t len = 0;
- if (di->di_dict != NULL && di->di_used == FT_ATOMIC_LOAD_SSIZE_RELAXED(di->di_dict->ma_used))
+ if (di->di_dict != NULL && di->di_used == GET_USED(di->di_dict))
len = FT_ATOMIC_LOAD_SSIZE_RELAXED(di->len);
return PyLong_FromSize_t(len);
}
Py_ssize_t i;
PyDictKeysObject *k;
- assert (PyDict_Check(d));
+ assert (PyAnyDict_Check(d));
ASSERT_DICT_LOCKED(d);
if (di->di_used != d->ma_used) {
PyObject *value;
Py_ssize_t i;
- assert (PyDict_Check(d));
+ assert (PyAnyDict_Check(d));
ASSERT_DICT_LOCKED(d);
if (di->di_used != d->ma_used) {
PyObject *key, *value;
Py_ssize_t i;
- assert (PyDict_Check(d));
+ assert (PyAnyDict_Check(d));
ASSERT_DICT_LOCKED(d);
if (di->di_used != d->ma_used) {
Py_ssize_t i;
PyDictKeysObject *k;
- assert (PyDict_Check(d));
+ assert (PyAnyDict_Check(d));
if (di->di_used != _Py_atomic_load_ssize_relaxed(&d->ma_used)) {
PyErr_SetString(PyExc_RuntimeError,
{
dictiterobject *di = (dictiterobject *)self;
- assert (PyDict_Check(d));
+ assert (PyAnyDict_Check(d));
ASSERT_DICT_LOCKED(d);
if (di->di_used != d->ma_used) {
dict___reversed___impl(PyDictObject *self)
/*[clinic end generated code: output=e674483336d1ed51 input=23210ef3477d8c4d]*/
{
- assert (PyDict_Check(self));
+ assert (PyAnyDict_Check(self));
return dictiter_new(self, &PyDictRevIterKey_Type);
}
_PyDictViewObject *dv = (_PyDictViewObject *)self;
Py_ssize_t len = 0;
if (dv->dv_dict != NULL)
- len = FT_ATOMIC_LOAD_SSIZE_RELAXED(dv->dv_dict->ma_used);
+ len = GET_USED(dv->dv_dict);
return len;
}
PyErr_BadInternalCall();
return NULL;
}
- if (!PyDict_Check(dict)) {
+ if (!PyAnyDict_Check(dict)) {
/* XXX Get rid of this restriction later */
PyErr_Format(PyExc_TypeError,
"%s() requires a dict argument, not '%s'",
if (PyDictKeys_Check(self)) {
// PySet_New() has fast path for the dict object.
PyObject *dict = (PyObject *)((_PyDictViewObject *)self)->dv_dict;
- if (PyDict_CheckExact(dict)) {
+ if (PyAnyDict_CheckExact(dict)) {
left = dict;
}
}
int
_PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value)
{
+ if (!PyDict_Check(dict)) {
+ PyErr_BadInternalCall();
+ return -1;
+ }
+
if (value == NULL) {
Py_hash_t hash = _PyObject_HashFast(name);
if (hash == -1) {
if (dict == NULL) {
return 1;
}
- return FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyDictObject *)dict)->ma_used) == 0;
+ return GET_USED((PyDictObject *)dict) == 0;
}
int
int
PyDict_Watch(int watcher_id, PyObject* dict)
{
- if (!PyDict_Check(dict)) {
+ if (!PyAnyDict_Check(dict)) {
PyErr_SetString(PyExc_ValueError, "Cannot watch non-dictionary");
return -1;
}
int
PyDict_Unwatch(int watcher_id, PyObject* dict)
{
- if (!PyDict_Check(dict)) {
+ if (!PyAnyDict_Check(dict)) {
PyErr_SetString(PyExc_ValueError, "Cannot watch non-dictionary");
return -1;
}
return 0;
}
#endif
+
+// --- frozendict implementation ---------------------------------------------
+
+static PyNumberMethods frozendict_as_number = {
+ .nb_or = frozendict_or,
+};
+
+static PyMappingMethods frozendict_as_mapping = {
+ .mp_length = dict_length,
+ .mp_subscript = dict_subscript,
+};
+
+static PyMethodDef frozendict_methods[] = {
+ DICT___CONTAINS___METHODDEF
+ {"__getitem__", dict_subscript, METH_O | METH_COEXIST, getitem__doc__},
+ DICT___SIZEOF___METHODDEF
+ DICT_GET_METHODDEF
+ DICT_KEYS_METHODDEF
+ DICT_ITEMS_METHODDEF
+ DICT_VALUES_METHODDEF
+ DICT_FROMKEYS_METHODDEF
+ DICT_COPY_METHODDEF
+ DICT___REVERSED___METHODDEF
+ {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
+ {NULL, NULL} /* sentinel */
+};
+
+
+static PyObject *
+frozendict_repr(PyObject *self)
+{
+ PyObject *repr = dict_repr(self);
+ if (repr == NULL) {
+ return NULL;
+ }
+ assert(PyUnicode_Check(repr));
+
+ PyObject *res = PyUnicode_FromFormat("%s(%U)",
+ Py_TYPE(self)->tp_name,
+ repr);
+ Py_DECREF(repr);
+ return res;
+}
+
+static Py_hash_t
+frozendict_hash(PyObject *op)
+{
+ PyFrozenDictObject *self = _PyFrozenDictObject_CAST(op);
+ Py_hash_t hash = FT_ATOMIC_LOAD_SSIZE_RELAXED(self->ma_hash);
+ if (hash != -1) {
+ return hash;
+ }
+
+ PyObject *items = _PyDictView_New(op, &PyDictItems_Type);
+ if (items == NULL) {
+ return -1;
+ }
+ PyObject *frozenset = PyFrozenSet_New(items);
+ Py_DECREF(items);
+ if (frozenset == NULL) {
+ return -1;
+ }
+
+ hash = PyObject_Hash(frozenset);
+ Py_DECREF(frozenset);
+ if (hash == -1) {
+ return -1;
+ }
+
+ FT_ATOMIC_STORE_SSIZE_RELAXED(self->ma_hash, hash);
+ return hash;
+}
+
+
+static PyObject *
+frozendict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyObject *d = dict_new(type, args, kwds);
+ if (d == NULL) {
+ return NULL;
+ }
+ PyFrozenDictObject *self = _PyFrozenDictObject_CAST(d);
+ self->ma_hash = -1;
+
+ if (args != NULL) {
+ if (dict_update_common(d, args, kwds, "frozendict") < 0) {
+ Py_DECREF(d);
+ return NULL;
+ }
+ }
+ else {
+ assert(kwds == NULL);
+ }
+
+ return d;
+}
+
+
+PyObject*
+PyFrozenDict_New(PyObject *iterable)
+{
+ if (iterable != NULL) {
+ PyObject *args = PyTuple_Pack(1, iterable);
+ if (args == NULL) {
+ return NULL;
+ }
+ PyObject *frozendict = frozendict_new(&PyFrozenDict_Type, args, NULL);
+ Py_DECREF(args);
+ return frozendict;
+ }
+ else {
+ PyObject *args = Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_TUPLE);
+ return frozendict_new(&PyFrozenDict_Type, args, NULL);
+ }
+}
+
+
+PyTypeObject PyFrozenDict_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ .tp_name = "frozendict",
+ .tp_basicsize = sizeof(PyFrozenDictObject),
+ .tp_dealloc = dict_dealloc,
+ .tp_repr = frozendict_repr,
+ .tp_as_number = &frozendict_as_number,
+ .tp_as_sequence = &dict_as_sequence,
+ .tp_as_mapping = &frozendict_as_mapping,
+ .tp_hash = frozendict_hash,
+ .tp_getattro = PyObject_GenericGetAttr,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC
+ | Py_TPFLAGS_BASETYPE
+ | _Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_MAPPING,
+ .tp_doc = dictionary_doc,
+ .tp_traverse = dict_traverse,
+ .tp_clear = dict_tp_clear,
+ .tp_richcompare = dict_richcompare,
+ .tp_iter = dict_iter,
+ .tp_methods = frozendict_methods,
+ .tp_init = dict_init,
+ .tp_alloc = _PyType_AllocNoTrack,
+ .tp_new = frozendict_new,
+ .tp_free = PyObject_GC_Del,
+ .tp_vectorcall = dict_vectorcall,
+ .tp_version_tag = _Py_TYPE_VERSION_FROZENDICT,
+};
if (PyUnicode_Check(op)) {
_PyUnicode_CheckConsistency(op, check_content);
}
- else if (PyDict_Check(op)) {
+ else if (PyAnyDict_Check(op)) {
_PyDict_CheckConsistency(op, check_content);
}
return 1;
&PyEnum_Type,
&PyFilter_Type,
&PyFloat_Type,
- &PyFrame_Type,
&PyFrameLocalsProxy_Type,
+ &PyFrame_Type,
+ &PyFrozenDict_Type,
&PyFrozenSet_Type,
&PyFunction_Type,
&PyGen_Type,
SETBUILTIN("enumerate", &PyEnum_Type);
SETBUILTIN("filter", &PyFilter_Type);
SETBUILTIN("float", &PyFloat_Type);
+ SETBUILTIN("frozendict", &PyFrozenDict_Type);
SETBUILTIN("frozenset", &PyFrozenSet_Type);
SETBUILTIN("property", &PyProperty_Type);
SETBUILTIN("int", &PyLong_Type);
#define TYPE_TUPLE '(' // See also TYPE_SMALL_TUPLE.
#define TYPE_LIST '['
#define TYPE_DICT '{'
+#define TYPE_FROZENDICT '}'
#define TYPE_CODE 'c'
#define TYPE_UNICODE 'u'
#define TYPE_UNKNOWN '?'
w_object(PyList_GET_ITEM(v, i), p);
}
}
- else if (PyDict_CheckExact(v)) {
+ else if (PyAnyDict_CheckExact(v)) {
Py_ssize_t pos;
PyObject *key, *value;
- W_TYPE(TYPE_DICT, p);
+ if (PyFrozenDict_CheckExact(v)) {
+ W_TYPE(TYPE_FROZENDICT, p);
+ }
+ else {
+ W_TYPE(TYPE_DICT, p);
+ }
/* This one is NULL object terminated! */
pos = 0;
while (PyDict_Next(v, &pos, &key, &value)) {
break;
case TYPE_DICT:
+ case TYPE_FROZENDICT:
v = PyDict_New();
R_REF(v);
if (v == NULL)
Py_DECREF(val);
}
if (PyErr_Occurred()) {
- Py_SETREF(v, NULL);
+ Py_CLEAR(v);
+ }
+ if (type == TYPE_FROZENDICT && v != NULL) {
+ PyObject *frozendict = PyFrozenDict_New(v);
+ if (frozendict != NULL) {
+ Py_SETREF(v, frozendict);
+ }
+ else {
+ Py_CLEAR(v);
+ }
}
retval = v;
break;
Modules/clinic/grpmodule.c.h grp_getgrgid _keywords -
Modules/clinic/grpmodule.c.h grp_getgrnam _keywords -
Objects/object.c - constants static PyObject*[]
+Objects/dictobject.c - PyFrozenDict_Type -
## False positives