views:

404

answers:

3

A frozen set is a frozenset.
A frozen list could be a tuple.
What would be a frozen dict ? An immutable, hashable dict.

I guess it could be something like collections.namedtuple, but namedtuple is more like a frozenkeys dict (an half-frozen dict). No ?

EDIT:
A frozendict should be a frozen DICT :
it should have keys, values, get, ... and support in, for, ...

+4  A: 

Assuming the keys and values of the dictionary are themselves immutable (e.g. strings) then:

>>> d
{'forever': 'atones', 'minks': 'cards', 'overhands': 'warranted', 
 'hardhearted': 'tartly', 'gradations': 'snorkeled'}
>>> t = tuple((k, d[k]) for k in sorted(d.keys()))
>>> hash(t)
1524953596
msw
This is a good, canonical, immutable representation of a dict (barring insane comparison behavior messing up the sort).
Mike Graham
+1, but nit: `tuple(sorted(d.iteritems()))` is nicer.
Devin Jeanpierre
@devin: agreed in full, but I'll let my post stand as an example that there's often an even better way.
msw
A: 

Do you mean "immutable" when you say "frozen" ? I think there is such a thing in django code, but not sure.

There is a recipe here Implementing an Immutable Dictionary

dzen
That recipe doesn't define `__hash__`, which makes it so much less useful.
Mike Graham
It also won't work because of that weird stuff with `del`'ing its `__init__`.
Mike Graham
It also doesn't work with subclasses of itself because it relies on `__foo` attributes of objects *other than `self`* to name-mangle right.
Mike Graham
+3  A: 

Python doesn't have a builtin frozendict type. It turns out this wouldn't be useful too often (though it would still probably be useful more often than frozenset is).

The most common reason to want such a type is when memoizing function calls for functions with unknown arguments. The most common solution to store a hashable equivalent of a dict (where the values are hashable) is something like tuple(sorted(kwargs.iteritems())).

This depends on the sorting not being a bit insane. Python cannot positively promise sorting will result in something reasonable here. (But it can't promise much else, so don't sweat it too much.)


You could easily enough make some sort of wrapper that works much like a dict. It might look something like

import collections

class FrozenDict(collections.Mapping):
    """Don't forget the docstrings!!"""

    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)
        self._hash = None

    def __iter__(self):
        return iter(self._d)

    def __len__(self):
        return len(self._d)

    def __getitem__(self, key):
        return self._d[key]

    def __hash__(self):
        # It would have been simpler and maybe more obvious to 
        # use hash(tuple(sorted(self._d.iteritems()))) from this discussion
        # so far, but this solution is O(n). I don't know what kind of 
        # n we are going to run into, but sometimes it's hard to resist the 
        # urge to optimize when it will gain improved algorithmic performance.
        if self._hash is None:
            self._hash = 0
            for key, value in self.iteritems():
                self._hash ^= hash(key)
                self._hash ^= hash(value)
        return self._hash

It should work great:

>>> x = FrozenDict(a=1, b=2)
>>> y = FrozenDict(a=1, b=2)
>>> x is y
False
>>> x == y
True
>>> x == {'a': 1, 'b': 2}
True
>>> d = {x: 'foo'}
>>> d[y]
'foo'
Mike Graham
I've never used `collections.Mapping`, but I do see that you have no comparison functions (eq, gt, lt, cmp, ...). Unless they are provided in the `collections.Mapping`, you don't need a hash function. Python will automatically use the object's id as the hash key.
Wallacoloo
@wallacoloo, `collections.Mapping` provides `__eq__` and `__ne__` mixins, **which we want**. We also *want* the hash. The defaults of identity comparison for equality and id for hash mean that two `FrozenDict` objects that hold the same data could not be used usefully as dict keys. I added some sample code that shows how this works nicely.
Mike Graham