Search code examples
pythondictionarymagic-methods

referencing __init__.__kwdefaults__ vs explicitly writing a dictionary


python 3, new to coding and python. i built a class dictionary with default values, then attempted to build a nested dictionary based on that class dictionary and encountered unexpected behaviour:

class User:

    def __init__(self, *, name=None, age=None, hobbies=[]):
        self.name = name
        self.age = age
        self.hobbies = hobbies

counter = 0
class_dict = {}

# building the nested dicts with default values
for num in range(0, 3):
    """
    1. referencing "User.__init__.__kwdefaults__"
    vs writting the actual dict directly into the nested dict
    2. {"name": None, "age": None, "hobbies": []}
    """
    class_dict.update({f"key_{num}": User.__init__.__kwdefaults__})
    # class_dict.update({f"key_{num}": {"name": None, "age": None, "hobbies": []}})
print("Blue print: " + str(class_dict))


# updating values in the nested dicts
for x in range(0, 3):   # simplified loop
    dict_key = "key_" + str(counter)
    try:
        if 1 < 2:   # simplified if check
            class_dict[dict_key]["name"] = "updated" + str(counter)
            print("inside loop: " + str(class_dict))
            counter = counter + 1
    except:
        continue

print("<<< final result: " + str(class_dict) + ">>>")  # end-result
  1. the " User.init.kwdefaults " version will updated the correct nested dicts keys inside the loop as expected, but as end result all 3 nested dicts "name" key store "updated2" as value. what ever gets changed in the last iteration of the loop gets changed in all the nested dicts.

  2. the actual dict " {"name": None, "age": None, "hobbies": []} " version also updates the correct nested dict keys inside the loop as expected. however the end result here for the "name" key in nested dict 1 stores the value "updated0", in nested dict 2 "updated1" and in nested 2 "updated2".

the end result of 2. is what i was aiming for and it took me a while to find the issue. i don't understand why both versions behave identically inside the loop but net different end results. is there a dunder/magic method to reference the class dictionary and get version 2. as end result?


Solution

  • The problem is that you are assigning the same subdict over and over again to every key as you could check by running this after your code:

    for x in range(0, 2):   # simplified loop
        dict_key = "key_" + str(x)
        dict_key2 = "key_" + str(x+1)
        print(f'subdict of key {x} is subdict of key {x+1}: {class_dict[dict_key] is class_dict[dict_key2]}')
    

    The output is:

    subdict of key 0 is subdict of key 1: True
    subdict of key 1 is subdict of key 2: True
    

    A solution would be by using deep copies as:

    import copy
    
    class User:
    
        def __init__(self, *, name=None, age=None, hobbies=[]):
            self.name = name
            self.age = age
            self.hobbies = hobbies
    
    counter = 0
    class_dict = {}
    
    # building the nested dicts with default values
    for num in range(0, 3):
        """
        1. referencing "User.__init__.__kwdefaults__"
        vs writting the actual dict directly into the nested dict
        2. {"name": None, "age": None, "hobbies": []}
        """
        class_dict.update({f"key_{num}": copy.deepcopy(User.__init__.__kwdefaults__)})
        # class_dict.update({f"key_{num}": {"name": None, "age": None, "hobbies": []}})
    print("Blue print: " + str(class_dict))