mirror of
https://github.com/python/cpython.git
synced 2026-02-22 07:00:51 +00:00
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>
This commit is contained in:
parent
63531a3867
commit
696cdfc0a2
18 changed files with 579 additions and 126 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
.. _dictobjects:
|
||||
|
||||
Dictionary Objects
|
||||
Dictionary objects
|
||||
------------------
|
||||
|
||||
.. index:: pair: object; dictionary
|
||||
|
|
@ -444,7 +444,7 @@ Dictionary Objects
|
|||
.. versionadded:: 3.12
|
||||
|
||||
|
||||
Dictionary View Objects
|
||||
Dictionary view objects
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. c:function:: int PyDictViewSet_Check(PyObject *op)
|
||||
|
|
@ -490,7 +490,58 @@ Dictionary View Objects
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -5305,8 +5305,8 @@ frozenset, a temporary one is created from *elem*.
|
|||
|
||||
.. _typesmapping:
|
||||
|
||||
Mapping Types --- :class:`dict`
|
||||
===============================
|
||||
Mapping types --- :class:`!dict`, :class:`!frozendict`
|
||||
======================================================
|
||||
|
||||
.. index::
|
||||
pair: object; mapping
|
||||
|
|
@ -5317,8 +5317,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.)
|
||||
|
||||
|
|
@ -5588,10 +5589,9 @@ can be used interchangeably to index the same dictionary entry.
|
|||
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:
|
||||
|
||||
|
|
@ -5839,6 +5839,41 @@ An example of dictionary view usage::
|
|||
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
|
||||
|
|
@ -6062,6 +6097,7 @@ list is non-exhaustive.
|
|||
* :class:`list`
|
||||
* :class:`dict`
|
||||
* :class:`set`
|
||||
* :class:`frozendict`
|
||||
* :class:`frozenset`
|
||||
* :class:`type`
|
||||
* :class:`asyncio.Future`
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ Summary -- Release highlights
|
|||
|
||||
* :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
|
||||
|
|
@ -180,6 +182,21 @@ raise :exc:`SyntaxError`).
|
|||
|
||||
(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
|
||||
|
|
@ -1525,6 +1542,16 @@ C API changes
|
|||
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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -42,7 +52,7 @@ PyAPI_FUNC(PyObject *) PyDict_SetDefault(
|
|||
/* 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);
|
||||
|
|
@ -93,3 +103,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);
|
||||
|
|
|
|||
|
|
@ -408,6 +408,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
|
||||
|
|
|
|||
|
|
@ -26,6 +26,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
|
||||
|
||||
|
|
|
|||
|
|
@ -823,6 +823,7 @@ def __eq__(self, other):
|
|||
|
||||
__reversed__ = None
|
||||
|
||||
Mapping.register(frozendict)
|
||||
Mapping.register(mappingproxy)
|
||||
Mapping.register(framelocalsproxy)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
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
|
||||
|
||||
|
|
@ -20,12 +20,9 @@ def _empty_mapping(self):
|
|||
"""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)
|
||||
|
|
@ -88,6 +85,72 @@ def check_iterandlist(iter, lst, ref):
|
|||
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()
|
||||
|
|
@ -130,46 +193,6 @@ def test_write(self):
|
|||
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()
|
||||
|
|
@ -265,22 +288,6 @@ def __next__(self):
|
|||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1723,6 +1723,58 @@ class Dict(dict):
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -742,7 +742,7 @@ def non_Python_modules(): r"""
|
|||
|
||||
>>> 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
|
||||
|
|
|
|||
|
|
@ -6127,7 +6127,8 @@ def _test_builtin_methods_have_signatures(self, cls, no_signature, unsupported_s
|
|||
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"}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Add built-in :class:`frozendict` type. Patch by Victor Stinner.
|
||||
|
|
@ -135,6 +135,10 @@ As a consequence of this, split keys have a maximum size of 16.
|
|||
#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"
|
||||
|
|
@ -278,6 +282,11 @@ load_keys_nentries(PyDictObject *mp)
|
|||
|
||||
#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)
|
||||
|
|
@ -654,7 +663,7 @@ _PyDict_CheckConsistency(PyObject *op, int check_content)
|
|||
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;
|
||||
|
|
@ -909,7 +918,7 @@ new_dict_with_shared_keys(PyDictKeysObject *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);
|
||||
|
|
@ -2293,7 +2302,7 @@ _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset,
|
|||
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;
|
||||
|
|
@ -2392,7 +2401,7 @@ _PyDict_GetItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
|
|||
PyDictObject *mp = (PyDictObject *)op;
|
||||
PyObject *value;
|
||||
|
||||
if (!PyDict_Check(op)) {
|
||||
if (!PyAnyDict_Check(op)) {
|
||||
PyErr_BadInternalCall();
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -2463,7 +2472,7 @@ _PyDict_GetItemRef_KnownHash(PyDictObject *op, PyObject *key, Py_hash_t hash, Py
|
|||
int
|
||||
PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result)
|
||||
{
|
||||
if (!PyDict_Check(op)) {
|
||||
if (!PyAnyDict_Check(op)) {
|
||||
PyErr_BadInternalCall();
|
||||
*result = NULL;
|
||||
return -1;
|
||||
|
|
@ -2519,7 +2528,7 @@ PyDict_GetItemWithError(PyObject *op, PyObject *key)
|
|||
PyDictObject*mp = (PyDictObject *)op;
|
||||
PyObject *value;
|
||||
|
||||
if (!PyDict_Check(op)) {
|
||||
if (!PyAnyDict_Check(op)) {
|
||||
PyErr_BadInternalCall();
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -2668,7 +2677,7 @@ setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
|
|||
|
||||
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);
|
||||
|
|
@ -2713,6 +2722,16 @@ PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value)
|
|||
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)
|
||||
{
|
||||
|
|
@ -2996,7 +3015,7 @@ _PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey,
|
|||
PyObject *key, *value;
|
||||
Py_hash_t hash;
|
||||
|
||||
if (!PyDict_Check(op))
|
||||
if (!PyAnyDict_Check(op))
|
||||
return 0;
|
||||
|
||||
mp = (PyDictObject *)op;
|
||||
|
|
@ -3265,8 +3284,8 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
|
|||
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);
|
||||
|
|
@ -3290,7 +3309,7 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
|
|||
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);
|
||||
|
|
@ -3460,7 +3479,7 @@ dict_repr(PyObject *self)
|
|||
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 *
|
||||
|
|
@ -3480,7 +3499,7 @@ dict_subscript(PyObject *self, PyObject *key)
|
|||
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(
|
||||
|
|
@ -3519,7 +3538,7 @@ keys_lock_held(PyObject *dict)
|
|||
{
|
||||
ASSERT_DICT_LOCKED(dict);
|
||||
|
||||
if (dict == NULL || !PyDict_Check(dict)) {
|
||||
if (dict == NULL || !PyAnyDict_Check(dict)) {
|
||||
PyErr_BadInternalCall();
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -3568,7 +3587,7 @@ values_lock_held(PyObject *dict)
|
|||
{
|
||||
ASSERT_DICT_LOCKED(dict);
|
||||
|
||||
if (dict == NULL || !PyDict_Check(dict)) {
|
||||
if (dict == NULL || !PyAnyDict_Check(dict)) {
|
||||
PyErr_BadInternalCall();
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -3616,7 +3635,7 @@ items_lock_held(PyObject *dict)
|
|||
{
|
||||
ASSERT_DICT_LOCKED(dict);
|
||||
|
||||
if (dict == NULL || !PyDict_Check(dict)) {
|
||||
if (dict == NULL || !PyAnyDict_Check(dict)) {
|
||||
PyErr_BadInternalCall();
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -3696,7 +3715,7 @@ dict_fromkeys_impl(PyTypeObject *type, PyObject *iterable, PyObject *value)
|
|||
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));
|
||||
|
|
@ -3762,7 +3781,7 @@ merge_from_seq2_lock_held(PyObject *d, PyObject *seq2, int override)
|
|||
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);
|
||||
|
|
@ -3958,13 +3977,13 @@ dict_merge(PyObject *a, PyObject *b, int override)
|
|||
* 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);
|
||||
|
|
@ -4075,6 +4094,9 @@ static PyObject *
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -4104,13 +4126,19 @@ copy_lock_held(PyObject *o)
|
|||
{
|
||||
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)) {
|
||||
|
|
@ -4119,7 +4147,13 @@ copy_lock_held(PyObject *o)
|
|||
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;
|
||||
|
|
@ -4132,13 +4166,18 @@ copy_lock_held(PyObject *o)
|
|||
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:
|
||||
|
||||
|
|
@ -4170,7 +4209,12 @@ copy_lock_held(PyObject *o)
|
|||
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)
|
||||
|
|
@ -4182,7 +4226,7 @@ copy_lock_held(PyObject *o)
|
|||
PyObject *
|
||||
PyDict_Copy(PyObject *o)
|
||||
{
|
||||
if (o == NULL || !PyDict_Check(o)) {
|
||||
if (o == NULL || !PyAnyDict_Check(o)) {
|
||||
PyErr_BadInternalCall();
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -4199,11 +4243,11 @@ PyDict_Copy(PyObject *o)
|
|||
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.
|
||||
|
|
@ -4289,7 +4333,7 @@ dict_richcompare(PyObject *v, PyObject *w, int op)
|
|||
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) {
|
||||
|
|
@ -4739,7 +4783,7 @@ dict___sizeof___impl(PyDictObject *self)
|
|||
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);
|
||||
|
|
@ -4753,6 +4797,29 @@ dict_or(PyObject *self, PyObject *other)
|
|||
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)
|
||||
{
|
||||
|
|
@ -4905,7 +4972,15 @@ dict_vectorcall(PyObject *type, PyObject * const*args,
|
|||
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;
|
||||
}
|
||||
|
|
@ -4918,7 +4993,8 @@ dict_vectorcall(PyObject *type, PyObject * const*args,
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
@ -4991,6 +5067,7 @@ PyTypeObject PyDict_Type = {
|
|||
.tp_version_tag = _Py_TYPE_VERSION_DICT,
|
||||
};
|
||||
|
||||
|
||||
/* For backward compatibility with old dictionary interface */
|
||||
|
||||
PyObject *
|
||||
|
|
@ -5073,7 +5150,7 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype)
|
|||
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 ||
|
||||
|
|
@ -5129,7 +5206,7 @@ dictiter_len(PyObject *self, PyObject *Py_UNUSED(ignored))
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
|
@ -5166,7 +5243,7 @@ dictiter_iternextkey_lock_held(PyDictObject *d, PyObject *self)
|
|||
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) {
|
||||
|
|
@ -5290,7 +5367,7 @@ dictiter_iternextvalue_lock_held(PyDictObject *d, PyObject *self)
|
|||
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) {
|
||||
|
|
@ -5412,7 +5489,7 @@ dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self,
|
|||
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) {
|
||||
|
|
@ -5518,7 +5595,7 @@ dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self,
|
|||
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,
|
||||
|
|
@ -5712,7 +5789,7 @@ dictreviter_iter_lock_held(PyDictObject *d, PyObject *self)
|
|||
{
|
||||
dictiterobject *di = (dictiterobject *)self;
|
||||
|
||||
assert (PyDict_Check(d));
|
||||
assert (PyAnyDict_Check(d));
|
||||
ASSERT_DICT_LOCKED(d);
|
||||
|
||||
if (di->di_used != d->ma_used) {
|
||||
|
|
@ -5842,7 +5919,7 @@ static PyObject *
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -5915,7 +5992,7 @@ dictview_len(PyObject *self)
|
|||
_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;
|
||||
}
|
||||
|
||||
|
|
@ -5927,7 +6004,7 @@ _PyDictView_New(PyObject *dict, PyTypeObject *type)
|
|||
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'",
|
||||
|
|
@ -6117,7 +6194,7 @@ dictviews_to_set(PyObject *self)
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -6847,6 +6924,11 @@ _PyObject_MaterializeManagedDict(PyObject *obj)
|
|||
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) {
|
||||
|
|
@ -7169,7 +7251,7 @@ _PyObject_IsInstanceDictEmpty(PyObject *obj)
|
|||
if (dict == NULL) {
|
||||
return 1;
|
||||
}
|
||||
return FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyDictObject *)dict)->ma_used) == 0;
|
||||
return GET_USED((PyDictObject *)dict) == 0;
|
||||
}
|
||||
|
||||
int
|
||||
|
|
@ -7631,7 +7713,7 @@ validate_watcher_id(PyInterpreterState *interp, int watcher_id)
|
|||
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;
|
||||
}
|
||||
|
|
@ -7646,7 +7728,7 @@ PyDict_Watch(int watcher_id, PyObject* dict)
|
|||
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;
|
||||
}
|
||||
|
|
@ -7743,3 +7825,147 @@ _PyObject_InlineValuesConsistencyCheck(PyObject *obj)
|
|||
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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ _PyObject_CheckConsistency(PyObject *op, int check_content)
|
|||
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;
|
||||
|
|
@ -2532,8 +2532,9 @@ static PyTypeObject* static_types[] = {
|
|||
&PyEnum_Type,
|
||||
&PyFilter_Type,
|
||||
&PyFloat_Type,
|
||||
&PyFrame_Type,
|
||||
&PyFrameLocalsProxy_Type,
|
||||
&PyFrame_Type,
|
||||
&PyFrozenDict_Type,
|
||||
&PyFrozenSet_Type,
|
||||
&PyFunction_Type,
|
||||
&PyGen_Type,
|
||||
|
|
|
|||
|
|
@ -3536,6 +3536,7 @@ _PyBuiltin_Init(PyInterpreterState *interp)
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ module marshal
|
|||
#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 '?'
|
||||
|
|
@ -575,10 +576,15 @@ w_complex_object(PyObject *v, char flag, WFILE *p)
|
|||
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)) {
|
||||
|
|
@ -1420,6 +1426,7 @@ r_object(RFILE *p)
|
|||
break;
|
||||
|
||||
case TYPE_DICT:
|
||||
case TYPE_FROZENDICT:
|
||||
v = PyDict_New();
|
||||
R_REF(v);
|
||||
if (v == NULL)
|
||||
|
|
@ -1443,7 +1450,16 @@ r_object(RFILE *p)
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -769,6 +769,7 @@ Modules/clinic/md5module.c.h _md5_md5 _keywords -
|
|||
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
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 4.
|
Loading…
Add table
Add a link
Reference in a new issue