A frozendict is a dictionary that cannot be modified after being created - but it is hashable and may serve as a member of a set or a key in a dictionary.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | * Quick'n'dirty version:
class hashabledict(dict):
def __hash__(self):
return hash(tuple(sorted(self.items())))
* Fancy version:
class frozendict(dict):
def _blocked_attribute(obj):
raise AttributeError, "A frozendict cannot be modified."
_blocked_attribute = property(_blocked_attribute)
__delitem__ = __setitem__ = clear = _blocked_attribute
pop = popitem = setdefault = update = _blocked_attribute
def __new__(cls, *args):
new = dict.__new__(cls)
dict.__init__(new, *args)
return new
def __init__(self, *args):
pass
def __hash__(self):
try:
return self._cached_hash
except AttributeError:
h = self._cached_hash = hash(tuple(sorted(self.items())))
return h
def __repr__(self):
return "frozendict(%s)" % dict.__repr__(self)
|
I came up with the quick'n'dirty version to eliminate duplicates in a list of dicts by converting it to a set. It doesn't actually verify that the dict is not modified and should be used with care.
The fancy version blocks access to methods that might change the dictionary and also caches the computed hash for better performance. The name frozendict is by analogy to the builtin frozenset type.
This implementation assumes that all dictionary keys and values are hashable and that the keys are fully comparable. It won't work with complex keys. Sorting is required because dictionary items() are returned in arbitrary order. An alternative implementation may combine item hashes using an order-independent function like xor or addition.
Ooops. I forgot about keyword args:
Dictionaries in arbitrary order. Is not the order consistent, as long as the dictionary is not modified in the meantime? This is what I get from the (version 2.3) Library Reference, section 2.3.7: Mappings, and it is consistent with my experience. If so, you really don't need to sort the items.
Dictionary order consistency. If you iterate over the same dictionary object two times in a row without changing it in the middle the order will stay the same. Python makes no guarantees that two different dictionaries testing as equal will have the same item order.
The __init__ method can be removed, and maybe the __hash__ method can be faster (and use less memory) using a .sort() instead of sorted(), and using if "_cached_hash" in self.__dict__: instead of the the try-except.
Nope. __init__ is there for a reason.
dict.__init__ is essentially equivalent to dict.update and could be used to modify the frozen dictionary. It's ignored rather than raising an exception because it will be called once on initialization.
Doesn't work when dict's values are mutable.
Extended version of frozent dict. This extended version of the code above will also "freeze" dictionaries and lists stored as values of the dictionary. If dictionaries/lists are found among the values, they will be handled recursively.
The hash function relies on sorting, but many data types (e.g. sets) do not sort consistently. A more reliable alternative is:
(In addition, for large data sets this ought to run in O(n) time rather than the O(n log n) of the original.)