1

I have a dictionary where I am constantly doing stuff like this in my code:

special_dict = {}

# ...

if username not in special_dict:
    special_dict[username] = {}
    for subkey in ["Subkey1", "Subkey2", "Subkey3"]:
        special_dict[username][subkey] = []  # or {}, etc, depending on usecase

Basically I want a dictionary where for every username, the value is yet another dictionary of three specific subkeys, and then those values are lists or sets or what have you.

I'm familiar with defaultdict but I am not sure how to make the "value type" here something very specific. Normally I do defaultdict(list) if I want every value to be a list by default, but is there a way to make the default not a list but in itself a specific type of dictionary?

Ideally, in the end what I want to be able to do is special_dict[username][subkey].append(item) and not have to worry about whether or not the username exists first, because if it doesn't, it'll become a key and have the three subkeys formed.

user525966
  • 325
  • 1
  • 2
  • 11
  • 2
    Why was this closed? This isn't merely a "how do you make nested dictionaries" question, come on. – user525966 Mar 14 '21 at 17:31
  • The linked duplicate is wrong. It deals mainly with flattening dictionaries. This may be a better duplicate: https://stackoverflow.com/questions/49435968/create-a-dict-if-its-not-already-created-and-then-append-to-it/49436378#49436378. (I don't see a way to change the duplicate link without reopening the question.) – luther Mar 14 '21 at 19:42

1 Answers1

2

You need a function that will create the structure you want, and pass this function as argument to defaultdict:

from collections import defaultdict

def name_subdict():
    return {'key1':[], 'key2':set(), 'key3':{}}

mydict = defaultdict(name_subdict)

mydict['John']['key1'].append(1)
mydict['John']['key2'].add(2)
mydict['Jane']['key3'][10] = 20

print(mydict)
# defaultdict(<function name_subdict at 0x7fcaf81193a0>, 
# {'John': {'key1': [1], 'key2': {2}, 'key3': {}}, 
#  'Jane': {'key1': [], 'key2': set(), 'key3': {10: 20}}})

To answer your comment: yes, you can pass the type of data you want to be used for all subkeys, as in mydict = name_subdict(list). There is only one caveat: the argument to defaultdict must be a function (or any callable) that takes no argument.

So, name_subdict(list) should return a function that will in turn create the structure.

The code would then be:

from collections import defaultdict

def name_subdict(data_type):
    # data type must be a callable like list, set, dict...
    def subdict_creator():
        return {key:data_type() for key in ['key1', 'key2', 'key3']}
    return subdict_creator
    
my_list_dict = defaultdict(name_subdict(list))
my_set_dict = defaultdict(name_subdict(set))

my_list_dict['John']['key1'].append(1)
my_list_dict['John']['key2'].append(2)

my_set_dict['Jane']['key3'].add(10)

print(my_list_dict)
# defaultdict(<function name_subdict.<locals>.subdict_creator at 0x7fcadbf27b80>, 
# {'John': {'key1': [1], 'key2': [2], 'key3': []}})

print(my_set_dict)
# defaultdict(<function name_subdict.<locals>.subdict_creator at 0x7fcadbbf25e0>, 
# {'Jane': {'key1': set(), 'key2': set(), 'key3': {10}}})
Thierry Lathuille
  • 21,301
  • 10
  • 35
  • 37
  • Is there a way to make the data-type passable? Like `mydict = defaultdict(name_subdict(list))` that returns `return {'key1':[], 'key2':[], 'key3':[]}` whereas if I did `mydict = defaultdict(name_subdict(set))` it would return `return {'key1':{}, 'key2':{}, 'key3':{}}` or whatever else, or does each one have to be handled with if-statements in name_subdict? – user525966 Mar 14 '21 at 17:29
  • 1
    Yes, you can, see the edited answer. – Thierry Lathuille Mar 14 '21 at 17:41
  • Thanks mate! Very useful information to know – user525966 Mar 14 '21 at 18:11