'Python: nested dictionary is changing all values at once

I need to generate nested dictionaries, so I wrote the function create_nested:

def create_nested():
    key_dict = {}
    for key in ['key1', 'key2']:
        key_dict[key] = 0

    nested_dict = {}
    for dictionary in ['dict1', 'dict2']:
        nested_dict[dictionary] = key_dict
    return nested_dict

Which will always return something like this:

{'dict1': {'key1': 0, 'key2': 0}, 'dict2': {'key1': 0, 'key2': 0}}

When a try to change one of the values this way:

x=create_nested()
x['dict1']['key2'] = 2

It gives me {'dict1': {'key1': 0, 'key2': 2}, 'dict2': {'key1': 0, 'key2': 2}}

Instead of {'dict1': {'key1': 0, 'key2': 2}, 'dict2': {'key1': 0, 'key2': 0}}

What am I doing wrong?



Solution 1:[1]

When you assign the key_dict to multiple values in nested_dict, they will refer to the same object in memory (I hope my terminology is correct. Python is Pass-by-object-reference, full explaination here https://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick/ )

for dictionary in ['dict1', 'dict2']:
        nested_dict[dictionary] = key_dict
    return nested_dict

now if I change nested_dict['dict1'] I'm changing the same memory that nested_dict['dict2'] knows about. In general, know which types are mutable, and expect them to change if another reference to it changes it.

A simple fix for dictionaries that only have immutable values is:

for dictionary_name in ['dict1', 'dict2']: # The variable name dictionary for a string is very confusing, so changing that.
        # create a new dictionary in memory
        nested_dict[dictionary_name] = {k: v for k, v in key_dict.items()}
    return nested_dict

If you have potentially values in key_dict that are mutable then you must make a deep copy. https://docs.python.org/3/library/copy.html

Solution 2:[2]

Your dict1 and dict2 keys are referencing to the same dict object created before so any change performed on it will impact both key's values.

If you want to assign to your keys two different dicts, you should copy it:

import copy
def create_nested():
    key_dict = {}
    for key in ['key1', 'key2']:
        key_dict[key] = 0
        nested_dict = {}
    for dictionary in ['dict1', 'dict2']:
        nested_dict[dictionary] = copy.deepcopy(key_dict)

deepcopy will copy recursively all the elements inside your dict and will create an independent copy for you, so any change performed on it will impact only itself.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Neil
Solution 2 Gabio