Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutable dictionary, only use as a key for another dictionary

Tags:

python

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?

like image 744
Daan Timmer Avatar asked Apr 03 '12 16:04

Daan Timmer


People also ask

Can a dictionary be a key for another dictionary Python?

However, neither a list nor another dictionary can serve as a dictionary key, because lists and dictionaries are mutable.

Why can only immutable values be used as keys in a Python dictionary?

Values can be any type of object, but keys must be immutable. This means keys could be integers, strings, or tuples, but not lists, because lists are mutable. Dictionaries themselves are mutable, so entries can be added, removed, and changed at any time.

Are Keys in dictionaries immutable?

What are Keys? As shown in the figure below, keys are immutable ( which cannot be changed ) data types that can be either strings or numbers. However, a key can not be a mutable data type, for example, a list. Keys are unique within a Dictionary and can not be duplicated inside a Dictionary.

Why do dictionary keys need to be immutable?

Why must dictionary keys be immutable? ¶ The hash table implementation of dictionaries uses a hash value calculated from the key value to find the key. If the key were a mutable object, its value could change, and thus its hash could also change.


1 Answers

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).

like image 109
Marcin Avatar answered Sep 23 '22 10:09

Marcin