Daan Timmer Daan Timmer - 18 days ago 5
Python Question

Immutable dictionary, only use as a key for another dictionary

I had the need to implement a hashable dict so I could use a dictionary as a key for another dictionary.

A few months ago I used this implementation: Python hashable dicts

However I got a notice from a colleague saying 'it is not really immutable, thus it is not safe. You can use it, but it does make me feel like a sad Panda'.

So I started looking around to create one that is immutable. I have no need to compare the 'key-dict' to another 'key-dict'. Its only use is as a key for another dictionary.

I have come up with the following:

class HashableDict(dict):
"""Hashable dict that can be used as a key in other dictionaries"""

def __new__(self, *args, **kwargs):
# create a new local dict, that will be used by the HashableDictBase closure class
immutableDict = dict(*args, **kwargs)

class HashableDictBase(object):
"""Hashable dict that can be used as a key in other dictionaries. This is now immutable"""

def __key(self):
"""Return a tuple of the current keys"""
return tuple((k, immutableDict[k]) for k in sorted(immutableDict))

def __hash__(self):
"""Return a hash of __key"""
return hash(self.__key())

def __eq__(self, other):
"""Compare two __keys"""
return self.__key() == other.__key() # pylint: disable-msg=W0212

def __repr__(self):
"""@see: dict.__repr__"""
return immutableDict.__repr__()

def __str__(self):
"""@see: dict.__str__"""
return immutableDict.__str__()

def __setattr__(self, *args):
raise TypeError("can't modify immutable instance")
__delattr__ = __setattr__

return HashableDictBase()


I used the following to test the functionality:

d = {"a" : 1}

a = HashableDict(d)
b = HashableDict({"b" : 2})

print a
d["b"] = 2
print a

c = HashableDict({"a" : 1})

test = {a : "value with a dict as key (key a)",
b : "value with a dict as key (key b)"}

print test[a]
print test[b]
print test[c]


which gives:


{'a': 1}

{'a': 1}

value with a dict as key (key a)

value with a dict as key (key b)

value with a dict as key (key a)


as output

Is this the 'best possible' immutable dictionary that I can use that satisfies my requirements? If not, what would be a better solution?

Answer

If you are only using it as a key for another dict, you could go for frozenset(mutabledict.items()). If you need to access the underlying mappings, you could then use that as the parameter to dict.

mutabledict = dict(zip('abc', range(3)))
immutable = frozenset(mutabledict.items())
read_frozen = dict(immutable)
read_frozen['a'] # => 1

Note that you could also combine this with a class derived from dict, and use the frozenset as the source of the hash, while disabling __setitem__, as suggested in another answer. (@RaymondHettinger's answer for code which does just that).