21

Is there a way to get a defaultdict to return the key by default? Or some data structure with equivalent behavior? I.e., after initializing dictionary d,

>>> d['a'] = 1
>>> d['a']
1
>>> d['b']
'b'
>>> d['c']
'c'

I've only seen default dictionaries take functions that don't take parameters, so I'm not sure if there's a solution other than creating a new kind of dictionary .

beardc
  • 20,283
  • 17
  • 76
  • 94

4 Answers4

25

I'd override the __missing__ method of dict:

>>> class MyDefaultDict(dict):
...     def __missing__(self, key):
...         self[key] = key
...         return key
...
>>> d = MyDefaultDict()
>>> d['joe']
'joe'
>>> d
{'joe': 'joe'}
pillmuncher
  • 10,094
  • 2
  • 35
  • 33
  • 2
    Is there a specific reason to store the key in addition to returning it? Unless I *need* to know which keys were queried, I see no reason to do this. – Joachim Sauer May 29 '12 at 10:25
  • @JoachimSauer: Normally one expects that if `d['joe']` doesn't raise a `KeyError`, then `'joe' in d` evaluates to `True`. If I didn't store the key that wouldn't be the case. – pillmuncher May 29 '12 at 12:26
  • 1
    But this solution makes `'joe' in d` evaluate to `False` *unless* you used `d['joe']` *before*. So the pure "read-only" access changes the result of *another* read-only access. I'm not sure that's any better. – Joachim Sauer May 29 '12 at 12:31
  • 2
    @JoachimSauer: You're right, but storing the key is consistent with how `collections.defaultdict` works. – pillmuncher May 29 '12 at 12:35
7

Edit: Oops, I just realized that code in my file originally came from another stackoverflow answer! https://stackoverflow.com/a/2912455/456876, go upvote that one.

This is what I use - it's a defaultdict variant that passes the key as an argument to the default-value factory function that's passed as an argument to init, instead of no arguments:

class keybased_defaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        else:
            value = self[key] = self.default_factory(key)
            return value

This is the use you want:

>>> d = keybased_defaultdict(lambda x: x)
>>> d[1]
1
>>> d['a']
'a'

Other possibilities:

>>> d = keybased_defaultdict(lambda x: len(x))
>>> d['a']
1
>>> d['abc']
3
Community
  • 1
  • 1
weronika
  • 2,561
  • 2
  • 24
  • 30
  • 2
    I was actually somewhat surprised to [re]discover that `defaultdict` does not already work like this... –  May 29 '12 at 00:41
  • @pst I suppose it theoretically makes some sense that's not the default behavior - if you're storing the same information in the key and the value, you may be using the wrong data type. That said, I do use this in my programs at work. – weronika May 29 '12 at 00:46
4

If you don't want to subclass dict, you can try using

d.get('a', 'a')
d.get('b', 'b')
d.get('c', 'c')

Which I think is clearer and less magical for this purpose

If you are a DRY fanatic and only have single char keys, you can do this :)

d.get(*'a'*2)
d.get(*'b'*2)
d.get(*'c'*2)
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
3

You'll likely have to write your own class that inherits from (or is similar to) defaultdict and override the __getitem__ method.

inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241