Search code examples
pythonlistdictionarynested-lists

Python Nested Dictionary append - Solved with defaultdict(lambda: defaultdict(set))


done this millions of times in other languages by python method for this is escaping me.

Basically reading some data from database. Includes data like ID, Fruits, Colors, Name

I need to create an object / dictionary key on Name. Name then holds lists / dictionaries of Fruits and Colors.

{"greenhouse1": {"fruits": {"apples", "oranges"}
                {"colors": {"red","orange"}}

I'm iterating through the db by row and the Name key will appear more than once. When it does fruits or colors may already be populated and need to append.

namedict = {}
db_rows = execute_query(cursor, args.store_proc)

for row in db_rows:
  db_name = row.id
  db_fruit = row.fruit
  db_color = row.color
  if db_name in namedict:
    namedict[db_name]["fruits"].append(db_fruit)
    namedict[db_name]["color"].append(db_color)
  else:
    namedict[db_name]["fruits"] = [db_fruit]
    namedict[db_name]["color"] = [db_color]

Solution

  • collections.defaultdict is your friend: If you access a new key of a defaultdict, it is automatically initialised with the provided function (list or set in this case). Because you want a dictionary (e.g. "greenhouse1") of dictionaries ("fruits", "colors") with lists as values (separate fruits and colors), we need a nested defaultdict. The following should work:

    from collections import defaultdict
    
    db = defaultdict(lambda: defaultdict(list))  # alternative: set instead of list
    db['greenhouse1']['fruits'].append('apples')  # use `add` for sets
    db['greenhouse1']['fruits'].append('oranges')
    db['greenhouse1']['colors'] = ["red", "orange"]
    
    db['greenhouse2']['fruits'].append('banana')
    
    print(db)
    # defaultdict(<function __main__.<lambda>()>,
    #             {'greenhouse1': defaultdict(list,
    #                          {'fruits': ['apples', 'oranges'],
    #                           'colors': ['red', 'orange']}),
    #              'greenhouse2': defaultdict(list, {'fruits': ['banana']})})
    

    A defaultdict works like a regular dict, so don't get confused with the strange looking output. E.g. to access the fruits of greenhouse1 you can simply write db['greenhouse1']['fruits'] and you get back a list (or set).