Search code examples
pythondefaultdict

One-step initialization of defaultdict that appends to list?


It would be convenient if a defaultdict could be initialized along the following lines

d = defaultdict(list, (('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a', 2),
   ('b', 3)))

to produce

defaultdict(<type 'list'>, {'a': [1, 2], 'c': [3], 'b': [2, 3], 'd': [4]})

Instead, I get

defaultdict(<type 'list'>, {'a': 2, 'c': 3, 'b': 3, 'd': 4})

To get what I need, I end up having to do this:

d = defaultdict(list)
for x, y in (('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a', 2), ('b', 3)):
    d[x].append(y)

This is IMO one step more than should be necessary, am I missing something here?


Solution

  • the behavior you describe would not be consistent with the defaultdicts other behaviors. Seems like what you want is FooDict such that

    >>> f = FooDict()
    >>> f['a'] = 1
    >>> f['a'] = 2
    >>> f['a']
    [1, 2]
    

    We can do that, but not with defaultdict; lets call it AppendDict

    import collections
    
    class AppendDict(collections.MutableMapping):
        def __init__(self, container=list, append=None, pairs=()):
            self.container = collections.defaultdict(container)
            self.append = append or list.append
            for key, value in pairs:
                self[key] = value
    
        def __setitem__(self, key, value):
            self.append(self.container[key], value)
    
        def __getitem__(self, key): return self.container[key]
        def __delitem__(self, key): del self.container[key]
        def __iter__(self): return iter(self.container)
        def __len__(self): return len(self.container)