2
\$\begingroup\$

I need a special dictionary that would do as follows:

  • if key is set to more than once, the values are a list of all set values. Such a list is returned as value on get.

  • if key is set only once, it is set to that value, and returned as value as well.

Sort of like MultiDict from Paste, but that does not seem to have the same interface as ordinary dict.

I wrote this:

class ModalDict(dict):

    def __init__(self, *args, **kwargs):
        class Speclist(list):
            pass
        self.speclist = Speclist
        dict.__init__(self, *args, **kwargs)

    def __getitem__(self, k):
        v = dict.__getitem__(self, k)
        if isinstance(v, self.speclist) and len(v) == 1:
            return v[0]
        return v

    def __setitem__(self, k, v):
        self.setdefault(k, self.speclist()).append(v)

Obviously I defined Speclist to differentiate it from regular list that someone may set as value.

Problems? Any better way to do this?

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

I think the second requirement (return the value for a key with only a single assignment as itself) is a bad requirement. It goes against Guido's guidance that passing different values (of the same type) to a function should avoid returning different types, and you can see how it breaks simple code like this, making it differ significantly from iterating d.items():

for k in d:       # assume d is an instance of your ModalDict
  for v in d[k]:  # can't iterate simple values, or accidentally iterates list values
    print(k, v)

Thus my recommendation would be to simply use d = collections.defaultdict(list) and always call d[k].append(value). If you need to prevent, say, accidentally replacing a list of values, you could make __setitem__ also do this or raise an exception. Since the implicit append seems like it would be surprising, I would rather raise an exception guiding towards the explicit append.

\$\endgroup\$
1
\$\begingroup\$

It can be solved in __setitem__, so the special __getitem__ will not affect the performance:

class ModalDict(dict):
    def __setitem__(self, k, v):
        if k in self:
            if isinstance(self[k], Speclist):
                v = self[k] + [v]
            else:
                v = Speclist([self[k], v])
        dict.__setitem__(self,k,v)

class Speclist(list):
    pass

If key is set only once, it is set to that value, and returned as value as well. So works like a normal dict:

>>> d = ModalDict()
>>> d['a'] = 1
>>> d['a']
1
>>> d # it prints the simple value too
{'a': 1}

If key is set to more than once, the values are a list of all set values:

>>> d['a'] = 2
>>> d['a']
[1, 2]
>>> d # it prints list too
{'a': [1, 2]}

>>> d['a'] = 3
>>> d['a']
[1, 2, 3]
>>> d
{'a': [1, 2, 3]}

Regular list is set as the value:

>>> d2 = ModalDict()
>>> d2['b'] = [1]
>>> d2['b']
[1]
>>> d2
{'b': [1]}

More values:

>>> d2['b'] = 2
>>> d2['b']
[[1], 2]
>>> d2
{'b': [[1], 2]}

>>> d2['b'] =  [3]
>>> d2['b']
[[1], 2, [3]]
>>> d2
{'b': [[1], 2, [3]]}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.