Search code examples
pythondictionaryrecursiondefaultdict

What is the difference between the following two implementaions of an arbitrary nested defaultdict of defaultdict in Python?


I am using the snippet from this answer, more specifically the one that uses a lambda.

I have been trying to implement this using the := operator in Python 3.8 and I ended up with two different implementations, both of which I believe does what I want.

Snippet 1 (using a lambda defined beforehand, same as snippet in linked answer)

from collections import defaultdict
from pprint import pprint

func = lambda:defaultdict(func)

infinite_nested_dicts_one = func()

infinite_nested_dicts_one[1][2][3][4][5]['a'] = '12345a'
infinite_nested_dicts_one[1][2][3][4][5]['b'] = '12345b'

print(infinite_nested_dicts_one[1][2][3][4][5]['a'])  # '12345a'
print(infinite_nested_dicts_one[1][2][3][4][5]['b'])  # '12345b'
pprint(infinite_nested_dicts_one)

Output 1

12345a
12345b
defaultdict(<function <lambda> at 0x000001A7405EA790>,
            {1: defaultdict(<function <lambda> at 0x000001A7405EA790>,
                            {2: defaultdict(<function <lambda> at 0x000001A7405EA790>,
                                            {3: defaultdict(<function <lambda> at 0x000001A7405EA790>,
                                                            {4: defaultdict(<function <lambda> at 0x000001A7405EA790>,
                                                                            {5: defaultdict(<function <lambda> at 0x000001A7405EA790>,
                                                                                            {'a': '12345a',
                                                                                             'b': '12345b'})})})})})})

Snippet 2 (using assignment operator)

from collections import defaultdict
from pprint import pprint

infinite_nested_dicts_two = (func:=defaultdict(lambda:func))

infinite_nested_dicts_two[1][2][3][4][5]['a'] = '12345a'
infinite_nested_dicts_two[1][2][3][4][5]['b'] = '12345b'

print(infinite_nested_dicts_two[1][2][3][4][5]['a'])  # '12345a'
print(infinite_nested_dicts_two[1][2][3][4][5]['b'])  # '12345b'
pprint(infinite_nested_dicts_two)

Output 2

12345a
12345b
defaultdict(<function <lambda> at 0x0000022F1A0EA790>,
            {1: <Recursion on defaultdict with id=2401323545280>,
             2: <Recursion on defaultdict with id=2401323545280>,
             3: <Recursion on defaultdict with id=2401323545280>,
             4: <Recursion on defaultdict with id=2401323545280>,
             5: <Recursion on defaultdict with id=2401323545280>,
             'a': '12345a',
             'b': '12345b'})

In both cases accessing the outer most defaultdict for [1][2][3][4][5]['a'] and [1][2][3][4][5]['b'] gives me the same output.

So the question is

  • Why is the pprint output different?
  • Are these identical or not?
  • Are they both arbitrary nested defaultdict of defaultdict?
  • Am I missing some details?

PS :

I am aware the the syntactical equivalent using := is infinite_nested_dicts_two = (func:=lambda:defaultdict(func))().

Do let me know if any clarification is needed. Many Thanks.


Solution

  • Your second version assigns a defaultdict instance to func (and, redundantly, to infinite_nested_dicts_two). Accordingly, instead of using func (which is not a function!) as its default-generating function, it uses a constant function that always returns funci.e., the dictionary itself is the default value. The necessary result is the heavily recursive structure revealed by pprint, since every level of indexing adds a key to, and returns, the same object.