Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5590,8 +5590,8 @@ can be used interchangeably to index the same dictionary entry.


.. seealso::
:class:`types.MappingProxyType` can be used to create a read-only view
of a :class:`dict`.
:class:`frozendict` and :class:`types.MappingProxyType` can be used to
create a read-only view of a :class:`dict`.

.. _thread-safety-dict:

Expand Down
8 changes: 4 additions & 4 deletions Lib/pydoc_data/topics.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Lib/test/pickletester.py
Original file line number Diff line number Diff line change
Expand Up @@ -3167,6 +3167,7 @@ def test_builtin_types(self):
'bytes': (3, 0),
'BuiltinImporter': (3, 3),
'str': (3, 4), # not interoperable with Python < 3.4
'frozendict': (3, 15),
}
for t in builtins.__dict__.values():
if isinstance(t, type) and not issubclass(t, BaseException):
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,9 @@ def test_update(self):
self.assertEqual(copy, frozendict({'x': 1}))

def test_repr(self):
d = frozendict()
self.assertEqual(repr(d), "frozendict()")

d = frozendict(x=1, y=2)
self.assertEqual(repr(d), "frozendict({'x': 1, 'y': 2})")

Expand All @@ -1775,6 +1778,15 @@ class MyFrozenDict(frozendict):
d = MyFrozenDict(x=1, y=2)
self.assertEqual(repr(d), "MyFrozenDict({'x': 1, 'y': 2})")

def test_hash(self):
# hash() doesn't rely on the items order
self.assertEqual(hash(frozendict(x=1, y=2)),
hash(frozendict(y=2, x=1)))

fd = frozendict(x=[1], y=[2])
with self.assertRaisesRegex(TypeError, "unhashable type: 'list'"):
hash(fd)


if __name__ == "__main__":
unittest.main()
3 changes: 2 additions & 1 deletion Lib/test/test_genericalias.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@

class BaseTest(unittest.TestCase):
"""Test basics."""
generic_types = [type, tuple, list, dict, set, frozenset, enumerate, memoryview,
generic_types = [type, tuple, list, dict, frozendict,
set, frozenset, enumerate, memoryview,
slice,
defaultdict, deque,
SequenceMatcher,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Optimize :meth:`!frozendict.fromkeys` to avoid unnecessary thread-safety operations
in frozendict cases. Patch by Donghee Na.
124 changes: 100 additions & 24 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2671,10 +2671,8 @@ _PyDict_LoadBuiltinsFromGlobals(PyObject *globals)

/* Consumes references to key and value */
static int
setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
anydict_setitem_take2(PyDictObject *mp, PyObject *key, PyObject *value)
{
ASSERT_DICT_LOCKED(mp);

assert(key);
assert(value);
assert(PyAnyDict_Check(mp));
Expand All @@ -2693,6 +2691,14 @@ setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
return insertdict(mp, key, hash, value);
}

/* Consumes references to key and value */
static int
setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
{
ASSERT_DICT_LOCKED(mp);
return anydict_setitem_take2(mp, key, value);
}

int
_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value)
{
Expand Down Expand Up @@ -3284,15 +3290,23 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
return NULL;


if (PyAnyDict_CheckExact(d)) {
if (PyAnyDict_CheckExact(iterable)) {
if (PyDict_CheckExact(d)) {
if (PyDict_CheckExact(iterable)) {
PyDictObject *mp = (PyDictObject *)d;

Py_BEGIN_CRITICAL_SECTION2(d, iterable);
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
Py_END_CRITICAL_SECTION2();
return d;
}
else if (PyFrozenDict_CheckExact(iterable)) {
PyDictObject *mp = (PyDictObject *)d;

Py_BEGIN_CRITICAL_SECTION(d);
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
Py_END_CRITICAL_SECTION();
return d;
}
else if (PyAnySet_CheckExact(iterable)) {
PyDictObject *mp = (PyDictObject *)d;

Expand All @@ -3302,14 +3316,37 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
return d;
}
}
else if (PyFrozenDict_CheckExact(d)) {
if (PyDict_CheckExact(iterable)) {
PyDictObject *mp = (PyDictObject *)d;

Py_BEGIN_CRITICAL_SECTION(iterable);
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
Py_END_CRITICAL_SECTION();
return d;
}
else if (PyFrozenDict_CheckExact(iterable)) {
PyDictObject *mp = (PyDictObject *)d;
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
return d;
}
else if (PyAnySet_CheckExact(iterable)) {
PyDictObject *mp = (PyDictObject *)d;

Py_BEGIN_CRITICAL_SECTION(iterable);
d = (PyObject *)dict_set_fromkeys(mp, iterable, value);
Py_END_CRITICAL_SECTION();
return d;
}
}

it = PyObject_GetIter(iterable);
if (it == NULL){
Py_DECREF(d);
return NULL;
}

if (PyAnyDict_CheckExact(d)) {
if (PyDict_CheckExact(d)) {
Py_BEGIN_CRITICAL_SECTION(d);
while ((key = PyIter_Next(it)) != NULL) {
status = setitem_lock_held((PyDictObject *)d, key, value);
Expand All @@ -3321,7 +3358,19 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
}
dict_iter_exit:;
Py_END_CRITICAL_SECTION();
} else {
}
else if (PyFrozenDict_CheckExact(d)) {
while ((key = PyIter_Next(it)) != NULL) {
// anydict_setitem_take2 consumes a reference to key
status = anydict_setitem_take2((PyDictObject *)d,
key, Py_NewRef(value));
if (status < 0) {
assert(PyErr_Occurred());
goto Fail;
}
}
}
else {
while ((key = PyIter_Next(it)) != NULL) {
status = PyObject_SetItem(d, key, value);
Py_DECREF(key);
Expand Down Expand Up @@ -7868,6 +7917,11 @@ static PyMethodDef frozendict_methods[] = {
static PyObject *
frozendict_repr(PyObject *self)
{
PyDictObject *mp = _PyAnyDict_CAST(self);
if (mp->ma_used == 0) {
return PyUnicode_FromFormat("%s()", Py_TYPE(self)->tp_name);
}

PyObject *repr = anydict_repr_impl(self);
if (repr == NULL) {
return NULL;
Expand All @@ -7881,33 +7935,55 @@ frozendict_repr(PyObject *self)
return res;
}

static Py_uhash_t
_shuffle_bits(Py_uhash_t h)
{
return ((h ^ 89869747UL) ^ (h << 16)) * 3644798167UL;
}

// Code copied from frozenset_hash()
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;
Py_hash_t shash = FT_ATOMIC_LOAD_SSIZE_RELAXED(self->ma_hash);
if (shash != -1) {
return shash;
}

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;
PyDictObject *mp = _PyAnyDict_CAST(op);
Py_uhash_t hash = 0;

PyObject *key, *value; // borrowed refs
Py_ssize_t pos = 0;
while (PyDict_Next(op, &pos, &key, &value)) {
Py_hash_t key_hash = PyObject_Hash(key);
if (key_hash == -1) {
return -1;
}
hash ^= _shuffle_bits(key_hash);

Py_hash_t value_hash = PyObject_Hash(value);
if (value_hash == -1) {
return -1;
}
hash ^= _shuffle_bits(value_hash);
}

hash = PyObject_Hash(frozenset);
Py_DECREF(frozenset);
if (hash == -1) {
return -1;
/* Factor in the number of active entries */
hash ^= ((Py_uhash_t)mp->ma_used + 1) * 1927868237UL;

/* Disperse patterns arising in nested frozendicts */
hash ^= (hash >> 11) ^ (hash >> 25);
hash = hash * 69069U + 907133923UL;

/* -1 is reserved as an error code */
if (hash == (Py_uhash_t)-1) {
hash = 590923713UL;
}

FT_ATOMIC_STORE_SSIZE_RELAXED(self->ma_hash, hash);
return hash;
FT_ATOMIC_STORE_SSIZE_RELAXED(self->ma_hash, (Py_hash_t)hash);
return (Py_hash_t)hash;
}


Expand Down
5 changes: 4 additions & 1 deletion Objects/setobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,10 @@ _shuffle_bits(Py_uhash_t h)
This hash algorithm can be used on either a frozenset or a set.
When it is used on a set, it computes the hash value of the equivalent
frozenset without creating a new frozenset object. */
frozenset without creating a new frozenset object.
If you update this code, update also frozendict_hash() which copied this
code. */

static Py_hash_t
frozenset_hash_impl(PyObject *self)
Expand Down
Loading