Casebash
Casebash

Reputation: 118812

Using a Python Dictionary as a Key (Non-nested)

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.

Upvotes: 42

Views: 53318

Answers (10)

cedrik
cedrik

Reputation: 559

Class name... OK :/

My solution is to create a class, with dict features, but implemented as a list with {key, value} objects. key and value can be anything then.

class DictKeyDictException(Exception):
    pass


class DictKeyDict():

    def __init__(self, *args):
        values = [self.__create_element(key, value) for key, value in args]
        self.__values__ = values

    def __setitem__(self, key, value):
        self.set(key, value)

    def __getitem__(self, key):
        return self.get(key)

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

    def __delitem__(self, key):
        keys = self.keys()

        if key in keys:
            index = keys.index(key)
            del self.__values__[index]

    def clear(self):
        self.__values__ = []

    def copy(self):
        return self.__values__.copy()

    def has_key(self, k):
        return k in self.keys()

    def update(self, *args, **kwargs):
        if kwargs:
            raise DictKeyDictException(f"no kwargs allowed in '{self.__class__.__name__}.update' method")
        for key, value in args:
            self[key] = value

        return self.__values__

    def __repr__(self) -> list:
        return repr(self.__values__)

    @classmethod
    def __create_element(cls, key, value):
        return {"key": key, "value": value}

    def set(self, key, value) -> None:
        keys = self.keys()

        if key in keys:
            index = keys.index(key)
            self.__values__[index] = self.__create_element(key, value)
        else:
            self.__values__.append(self.__create_element(key, value))

        return self.__values__

    def keys(self):
        return [dict_key_value["key"] for dict_key_value in self.__values__]

    def values(self):
        return [value["value"] for value in self.__values__]

    def items(self):
        return [(dict_key_value["key"], dict_key_value["value"]) for dict_key_value in self.__values__]

    def pop(self, key, default=None):
        keys = self.keys()

        if key in keys:
            index = keys.index(key)
            value = self.__values__.pop(index)["value"]
        else:
            value = default

        return value

    def get(self, key, default=None):
        keys = self.keys()

        if key in keys:
            index = keys.index(key)
            value = self.__values__[index]["value"]
        else:
            value = default

        return value

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

and usage :

dad = {"name": "dad"}
mom = {"name": "mom"}
boy = {"name": "son"}
girl = {"name": "daughter"}

# set
family = DictKeyDict()
family[dad] = {"age": 44}
family[mom] = {"age": 43}
# or
family.set(dad, {"age": 44, "children": [boy, girl]})
# or
family = DictKeyDict(
    (dad, {"age": 44, "children": [boy, girl]}),
    (mom, {"age": 43, "children": [boy, girl]}),
)

# update
family.update((mom, {"age": 33}))  # oups sorry miss /!\ loose my children

family.set({"pet": "cutty"}, "cat")
del family[{"pet": "cutty"}]  # cutty left...

family.set({"pet": "buddy"}, "dog")
family[{"pet": "buddy"}] = "wolf"  # buddy was not a dog

print(family.keys())
print(family.values())
for k, v in family.items():
    print(k, v)

Upvotes: 0

coder
coder

Reputation: 930

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

Upvotes: 1

Andrey Vlasovskikh
Andrey Vlasovskikh

Reputation: 16838

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

Upvotes: 85

idanliv
idanliv

Reputation: 1

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

Upvotes: 0

S.Lott
S.Lott

Reputation: 391852

To turn a someDictionary into a key, do this

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

You can easily reverse this with dict( key )

Upvotes: 7

Frank Bechmann
Frank Bechmann

Reputation: 81

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

Upvotes: 3

Michael Dillon
Michael Dillon

Reputation: 32392

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.

Upvotes: 8

Brian
Brian

Reputation: 119231

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

Upvotes: 4

Vissu
Vissu

Reputation: 328

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]

Upvotes: -1

Daniel Roseman
Daniel Roseman

Reputation: 599620

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

Upvotes: 0

Related Questions