35

Python doesn't allow dictionaries to be used as keys in other dictionaries. Is there a workaround for using non-nested dictionaries as keys?

The general problem with more complicated non-hashable objects and my specific use case has been moved here. My original description of my use case was incorrect.

Community
  • 1
  • 1
Casebash
  • 100,511
  • 79
  • 236
  • 337
  • The sort is necessary as python dictionaries are not ordered by default – Casebash Oct 21 '09 at 12:48
  • 4
    This looks to me like a design problem. Can you give an example where it makes sense to use a dictionary as a key? – Jonathan Feinberg Oct 21 '09 at 12:52
  • I think the function should take named tuples (or class instances) instead of nested dicts. You wouldn't have that problem then. – Jochen Ritzel Oct 21 '09 at 13:29
  • 1
    I guess you meant `sorted`, and it returns a generator so you need to make a `list` out of it. – Andrey Vlasovskikh Oct 21 '09 at 13:32
  • 1
    Your exact use case sounds exactly like memoization. There are recipes for that, and one of your answers suggests it. If I am just not understanding, could you explain why you don't just use a memoize recipe? – steveha Oct 21 '09 at 21:47
  • One key thing that I want to be able to do is allow a function to memorize other results. For example if I have a function to calculate the average, then it will also calculate a standard deviation at the same time and I want to store both of these values and to be able to access one when I have want="stddev" and another when as want="average" – Casebash Oct 21 '09 at 23:37

9 Answers9

67

If you have a really immutable dictionary (although it isn't clear to me why you don't just use a list of pairs: e.g. [('content-type', 'text/plain'), ('host', 'example.com')]), then you may convert your dict into:

  1. A tuple of pairs. You've already done that in your question. A tuple is required instead of list because the results rely on the ordering and the immutability of the elements.

    >>> tuple(sorted(a.items()))
    
  2. A frozen set. It is a more suitable approach from the mathematical point of view, as it requires only the equality relation on the elements of your immutable dict, while the first approach requires the ordering relation besides equality.

    >>> frozenset(a.items())
    
Andrey Vlasovskikh
  • 15,474
  • 6
  • 38
  • 59
  • 3
    +1: Good point on ordering. A dictionary can always be transformed to a frozenset because the keys must be unique, assuring each tuple will be preserved in the set. Elegant. – S.Lott Oct 21 '09 at 15:23
  • This is a nice solution which is more general than than the specific problem I posed, but doesn't this doesn't handle dictionaries within dictionaries. – Casebash Oct 23 '09 at 06:36
  • Now moved this to a new question – Casebash Oct 23 '09 at 09:16
  • 3
    Note that, Frozen set solution will not work, if the dict contains list or any other mutable object as values. For ex: a={'key1': 'val1', 'key2': ['val2', 'val3']} – AnukuL Aug 08 '17 at 03:36
8

If I needed to use dictionaries as keys, I would flatten the dictionary into a tuple of tuples.

You might find this SO question useful: What is the best way to implement nested dictionaries?

And here is an example of a flatten module that will flatten dictionaries: http://yawpycrypto.sourceforge.net/html/public/Flatten.Flatten-module.html

I don't fully understand your use case and I suspect that you are trying to prematurely optimize something that doesn't need optimization.

Community
  • 1
  • 1
Michael Dillon
  • 30,332
  • 5
  • 65
  • 99
  • +1: `tuple( someDictionary.items() )` works really, really well for making a dictionary into an immutable key. – S.Lott Oct 21 '09 at 13:42
  • 8
    Make that `tuple(sorted(somedictionary.items())` - the order of keys is not guaranteed, which means equal dicts might produce different reprs by listing the items in a different order. – Brian Oct 21 '09 at 13:59
  • 2
    Sorting in important. To see why you have to find 2 different key values with equal hash (it's probably hard to find for strings, but can be easily achieved with user defined objects), then construct 2 equal dictionaries by inserting them in different order. You'll get equal dictionaries with different order in `.items()`. – Denis Otkidach Oct 21 '09 at 14:29
  • The difficulty with this is that each dictionary has to be sorted – Casebash Oct 23 '09 at 06:50
6

To turn a someDictionary into a key, do this

key = tuple(sorted(someDictionary .items())

You can easily reverse this with dict( key )

S.Lott
  • 359,791
  • 75
  • 487
  • 757
4

One way to do this would be to subclass the dict and provide a hash method. ie:

class HashableDict(dict):
    def __hash__(self):
        return hash(tuple(sorted(self.iteritems())))

>>> d = HashableDict(a=1, b=2)
>>> d2 = { d : "foo"}
>>> d2[HashableDict(a=1, b=2)]
"foo"

However, bear in mind the reasons why dicts (or any mutable types) don't do this: mutating the object after it has been added to a hashtable will change the hash, which means the dict will now have it in the wrong bucket, and so incorrect results will be returned.

If you go this route, either be very sure that dicts will never change after they have been put in the other dictionary, or actively prevent them (eg. check that the hash never changes after the first call to __hash__, and throw an exception if not.)

Brian
  • 107,377
  • 28
  • 104
  • 109
3

Hmm, isn't your use case just memoizing function calls? Using a decorator, you will have easy support for arbitrary functions. And yes, they often pickle the arguments, and using circular reasoning, this works for non-standard types as long as they can be pickled.

See e.g. this memoization sample

1

I'll sum up the options and add one of my own, you can :

  • make a subclass to dict and provide a hash function
  • flatten the dict into a tuple
  • pickle the dict
  • convert the Dict into a string using the json module (as shown below)
import json
Dict = {'key' :'value123'}
stringifiedDict = json.dumps(Dict)
print(stringifiedDict)
# {"key": "value123"}
newDict = {stringifiedDict: 12345}
print(newDict[stringifiedDict])
# 12345
for key, val in newDict.items():
    print(json.loads(key))
    # {'key': 'value123'}
    print(json.loads(key)['key'])
    # value123
coder
  • 309
  • 3
  • 7
0

I don't see why you'd ever want to do this, but if you really really do need to, you could try pickling the dictionary:

mydict = {"a":1, "b":{"c":10}}
import pickle
key = pickle.dumps(mydict)

d[key] = value
Daniel Roseman
  • 541,889
  • 55
  • 754
  • 786
0

this function will convert a nested dictionary to an immutable tuple of tuples which you can use as a key:

def convert_dictionary_tuple(input_dict):
    """
    this function receives a nested dictionary and convert it to an immutable tuple of tuples with all the given
    dictionary data
    :param input_dict: a nested dictionary
    :return: immutable tuple of tuples with all the given dictionary data
    """
    tuples_dict = {}
    for key, value in input_dict.iteritems():
        if isinstance(value, dict):
            tuples_dict[key] = convert_dictionary_tuple(value)
        elif isinstance(value, list):
            tuples_dict[key] = tuple([convert_dictionary_tuple(v) if isinstance(v, dict) else v for v in value])
        else:
            tuples_dict[key] = value

    return tuple(sorted(tuples_dict.items()))
-1

I don't know whether I understand your question correctly, but i'll give it a try

    d[repr(a)]=value

You can interate over the dictionary like this

for el1 in d:
        for el2 in eval(el1):
                print el2,eval(el1)[el2]
Vissu
  • 318
  • 1
  • 2
  • 8
  • I think repl(a) would be better here as str might not be unique – mmmmmm Oct 21 '09 at 13:10
  • `repr` of the different objects might not be different. The "difference" among Python objects is usually coded as `__eq__`, not `__repr__`. – Andrey Vlasovskikh Oct 21 '09 at 13:29
  • 3
    `repr` and `str` are actually the same for dicts anyway. However, you could run into trouble this way - it's possible to get dicts with different internal state so that, while they contain the same items, they list their keys in a different order, and would thus produce a different key. You'll also run into trouble if you store objects without the property that `repr(x)==repr(y)` <=> x==y in the dict (eg. most user created classes). – Brian Oct 21 '09 at 13:37
  • 3
    -1: Use `tuple( someDictionary.items() )` instead of `repr`. it gives you a structure that can be trivially transformed back into a dictionary without resorting to `eval`. – S.Lott Oct 21 '09 at 13:55
  • This won't work when you get hash collision. See my comment to other answer to see how to demonstrate the problem. – Denis Otkidach Oct 21 '09 at 14:34