Search code examples
pythonpython-collections

Python defaultdict(default) vs dict.get(key, default)


Suppose I want to create a dict (or dict-like object) that returns a default value if I attempt to access a key that's not in the dict.

I can do this either by using a defaultdict:

from collections import defaultdict

foo = defaultdict(lambda: "bar")
print(foo["hello"]) # "bar"

or by using a regular dict and always using dict.get(key, default) to retrieve values:

foo = dict()
print(foo.get("hello", "bar")) # "bar"
print(foo["hello"]) # KeyError (as expected)

Other than the obvious ergonomic overhead of having to remember to use .get() with a default value instead of the expected bracket syntax, what's the difference between these 2 approaches?


Solution

  • Asides from the ergonomics of having .get everwhere, one important difference is if you lookup a missing key in defaultdict it will insert a new element into itself rather than just returning the default. The most important implications of this are:

    • Later iterations will retrieve all keys looked up in a defaultdict
    • As more ends up stored in the dictionary, more memory is typically used
    • Mutation of the default will store that mutation in a defaultdict, with .get the default is lost unless stored explicty
    from collections import defaultdict 
     
    default_foo = defaultdict(list) 
    dict_foo = dict()                                                                                                                                                                                                                                                                                           
    
    for i in range(1024): 
        default_foo[i] 
        dict_foo.get(i, []) 
                                                                                                                                                                                                                                                                                                     
    print(len(default_foo.items())) # 1024
    print(len(dict_foo.items())) # 0
    
    # Defaults in defaultdict's can be mutated where as with .get mutations are lost
    default_foo[1025].append("123")
    dict_foo.get(1025, []).append("123")
    
    print(default_foo[1025]) # ["123"]
    print(dict_foo.get(1025, [])) # []