16

collections.defaultdict is great. Especially in conjunction with lambda:

>>> import collections
>>> a = collections.defaultdict(lambda : [None,None])
>>> a['foo']
[None, None]

Is there a way to use the key given (e.g. 'foo') in the lambda? For example (doesn't work):

>>> a = collections.defaultdict(lambda : [None]*key)
>>> a[1]
[None]
>>> a[2]
[None, None]
>>> a
defaultdict(<function <lambda> at 0x02984170>, {1: [None], 2: [None, None]})
Chris Johnson
  • 20,650
  • 6
  • 81
  • 80
Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359

3 Answers3

32

You probably want __missing__ which is called on dict whenever you try to access an item not present in the dict; the vanilla __missing__ raises an exception, but you could do whatever you like in a subclass:

class A(dict):
    def __missing__(self, key):
        value = self[key] = [None] * key
        return value
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
4

Combining the answers from SingleNegationElimination and rplnt, and the defaultdict documentation, I used the following solution.

import collections
class KeyBasedDefaultDict(collections.defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        self[key] = self.default_factory(key)
        return self[key]

The body of the method could possibly just be return self.default_factory(key), but the extra code makes sure to replicate all defaultdict behaviour.

Usage as described in question:

d = KeyBasedDefaultDict(lambda key: [None] * key)
d[1]
> [None]
d[2]
> [None, None]
1

This will work as requested, although it's probably not the best solution (you need to initialize it with default call and then not use it). It could probably be fixed with overriding some other method(s).

class NoneDict(collections.defaultdict):
    def __setitem__(self, key, value):
        super(NoneDict, self).__setitem__(key, key*[None])
rplnt
  • 2,341
  • 16
  • 14