pythonpython-3.xdictionarycollections

Python's collections.defaultdict returns default value correctly unless the key isn't referenced first


Basically if I set a defaultdict and reference it by key it will work fine and return the value that I set as default. However if I use a .get() on it the default value is not returned. Here is the simplest example I can give of it.

basedict = defaultdict(dict)
assert(basedict['junk'] == {}) # Pass
assert(basedict.get('junk') == {}) # Pass
assert(basedict.get('popcorn') == {}) # Fail

For completion, I was working with dicts of dicts and my code where I initially encountered the problem looked a bit more like this

from collections import defaultdict

basedict = defaultdict(dict)
assert(basedict['junk'] == {})

basedict[69] = defaultdict(lambda: 1000)
assert(basedict[69]['junk'] == 1000)
assert(basedict[69].get('junk') == 1000)
assert(basedict[69].get('junk', 420) == 1000)

# The above works fine, but if I call .get on a default dict using a key I've
# never referenced before it returns None or the .get supplied default value
assert(basedict[69].get('garbage') == 1000) # Returns None
assert(basedict[69].get('garbage', 420) == 1000) # Returns 420
assert(basedict[69].get('dumpster', 420) == 1000) # Returns 420

# But if I place a an assert before the calling .get(garbage) that 
# checks basedict[69]['garbage'] then the asserts after work until I get 
# to 'dumpster' at which point it fails again
# It also fails if I set the defaultdict to something other than a lambda
basedict[7] = defaultdict(dict)
assert(basedict[7]['taco'] == {}) # Pass
assert(basedict[7].get('taco') == {}) # Pass
assert(basedict[7].get('burrito') == {}) # Fail

Solution

  • defaultdict.get() does not populate the key, no. This is by design, as it would otherwise defeat the purpose of that method. The same applies to membership tests:

    >>> from collections import defaultdict
    >>> d = defaultdict(dict)
    >>> 'foo' in d
    False
    >>> d['foo']
    {}
    >>> 'foo' in d
    True
    

    Use defaultdict.__getitem__ (e.g. defaultdict_instance[key]) if you need the default factory to be called for missing keys.

    If you needed to set a default value other than what the factory provides, use dict.setdefault():

    >>> d = defaultdict(dict)
    >>> d.setdefault('bar', 42)
    42
    >>> d.setdefault('bar', 30)
    42
    >>> d
    defaultdict(<type 'dict'>, {'bar': 42})
    

    If all you needed was to get a default value, use dict.get():

    >>> d = defaultdict(dict)
    >>> d.get('bar', {}).get('baz', 1000)
    1000
    

    Note that I chained the .get() calls; the first .get() returns an empty dictionary if the first key is missing.