Jonathon Hill Jonathon Hill - 3 months ago 22
Python Question

Why can a lambda for collections.defaultdict have no arguments?

In python I have the following line of code:

my_var = collections.defaultdict(lambda: collections.defaultdict(collections.defaultdict(float)))


I don't understand what it is doing. One of the things I don't understand is how we have not specified any variables for the lambda function.

Answer

The default_factory argument to defaultdict should be a function that doesn't accept any arguments which creates and returns an object which will become the value of any non-existent entries that get referenced.

One way to provide such a function is to create an anonymous one using an inline lambda expression. Another way is to use the name of a built-in function the creates and returns one of the built-in types, like float, int, tuple, etc. When any of those are called without an argument, the value of the object returned will have some default value — typically something like zero or empty.

First of all, you can make code like you have more readable by formatting it like this:

my_var = collections.defaultdict(
            lambda: collections.defaultdict(
                collections.defaultdict(float)))

However, (in either form) it's incorrect, since some of the defaultdict calls don't have callable function arguments. Because Python is an interpreted language you won't know this until you try to use my_var. For example something like:

my_var['level1']['level2']['level3'] = 42.

would result in:

TypeError: first argument must be callable or None

This can be fixed by adding another lambda:

my_var = collections.defaultdict(
            lambda: collections.defaultdict(
                lambda: collections.defaultdict(float)))  # need lambda here, too

and afterwards the preceding assignment will won't be considered an error.

Since defaultdict is a subclass of the built-in dict class, you can use the json module to pretty-print instances of one:

import collections

my_var = collections.defaultdict(
            lambda: collections.defaultdict(
                lambda: collections.defaultdict(float)))

import json  # for pretty printing dictionaries

# Auto-creates the 3 levels of dictionaries needed.
my_var['level1']['level2']['level3A'] = 42.

# Similar to above but additionally auto-creates a
# my_var['level1']['level2']['level3B'] entry with default value of 0.0
my_var['level1']['level2']['level3C'] = my_var['level1']['level2']['level3B'] + 1

print(json.dumps(my_var, indent=4))

Output:

{
    "level1": {
        "level2": {
            "level3A": 42.0, 
            "level3B": 0.0, 
            "level3C": 1.0
        }
    }
}

I think you should be able to understand what it does now — but let me know if not.