19

So the defaultdict documentation mentions that, if an item is missing, the value returned by default_factory "is inserted in the dictionary for the key, and returned." That's great most of the time, but what I actually want in this case is for the value to be returned but not inserted into the defaultdict.

I figured I could probably subclass defaultdict and override... I guess __missing__? Not sure. What's the best way to go about this?

Thanks in advance.

Chad
  • 1,794
  • 1
  • 17
  • 30

2 Answers2

18

You can subclass dict and implement __missing__:

class missingdict(dict):
    def __missing__(self, key):
        return 'default'  # note, does *not* set self[key]

Demo:

>>> d = missingdict()
>>> d['foo']
'default'
>>> d
{}

You could subclass defaultdict too, you'd get the factory handling plus copy and pickle support thrown in:

from collections import defaultdict

class missingdict(defaultdict):
    def __missing__(self, key):
        return self.default_factory() 

Demo:

>>> from collections import defaultdict
>>> class missingdict(defaultdict):
...     def __missing__(self, key):
...         return self.default_factory() 
... 
>>> d = missingdict(list)
>>> d['foo']
[]
>>> d
defaultdict(<type 'list'>, {})

but, as you can see, the __repr__ does lie about its name.

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • That's perfect. I'll probably go with the defaultdict route to get the copy support. Thanks! – Chad Jul 30 '13 at 20:56
  • Can I set a default value for `d.pop("unloaded_key")`? – Lerner Zhang Feb 14 '17 at 10:31
  • 1
    @Lerner: `dict.pop()` takes a second argument for the default. `dict.pop()` will *not* consult `__missing__`, as do most other methods; only `dict.__getitem__` does (so `d[missingkey]`). – Martijn Pieters Feb 14 '17 at 10:40
  • @MartijnPieters I hope that there exists a magic function by which I can set a default value for other methods like dict.pop("missingkey"). :( Is it possible? – Lerner Zhang Feb 14 '17 at 10:47
  • 2
    @Lerner: There is not. Just create your own; you are already subclassing `dict`, so just add `def pop(self, *args): try: return super().pop(*args) except KeyError: return self.__missing__()`. Adjust as needed if `__missing__` sets the key in the dictionary first. – Martijn Pieters Feb 14 '17 at 11:33
  • @MartijnPieters You're awesome guy. – Lerner Zhang Feb 14 '17 at 11:46
0

It's even simpler than subclassing. While dict[key] adds the key, dict.get(key, default=None) will NOT add the key to the dictionary:

import collections

d = collections.defaultdict(int)

assert d['test1'] == 0

print(d)
# defaultdict(<class 'int'>, {'test1': 0})

assert d.get('test2', 0) == 0
# for a more generic version, use `d.default_factory()` instead of 0

print(d)
# defaultdict(<class 'int'>, {'test1': 0})
# => did NOT insert a key for 'test2' =)
xjcl
  • 12,848
  • 6
  • 67
  • 89
  • In case there is objections that this goes against the purpose of using a DEFAULTdict -- in a way yes, but in my particular use case I'm also utilizing a defaultdict update à la `d[key] += 1` somewhere else in the code so it's still justified over a vanilla dict :-) – xjcl Jan 18 '20 at 06:36
  • Just to be clear, are you using `d[key] += 1` to insert `default+1` at `d[key]`, while intending `value = d[key]` to return the default without modifying the dictionary? – Elias Hasle May 18 '21 at 13:19
  • `d[key]` does insert the default key (0), `d.get(key, 0)` does not – xjcl May 18 '21 at 17:55