简体   繁体   中英

Python: setting values of a list of nested dictionaries in class instances

I have a burning question that I just can't figure out. Let's say you have a class, and this class takes in as input a list of nested dictionaries. It initializes one of the keys of each of these dictionaries with a new, empty dictionary. Later, I want to set one of the nested values of an object in the list as something. For some reason this seems to affect other objects in the list?

I know that's sounds very convoluted, so here's an example:

class Tester():
    def __init__(self, stuff):

        # stuff is a list of dictionaries
        self.stuff = stuff

        # Each item in the list should be initialized to this dictionary
        inside_init_dict = {'test': True}
        for x in self.stuff:
            x['info'] = inside_init_dict

if __name__ == '__main__':
    new_stuff = [{'info': {}}, {'info': {}}, {'info': {}}]
    mytest = Tester(new_stuff)

    print(mytest.stuff)
    # >>> [{'info': {'test': True}}, {'info': {'test': True}}, {'info': {'test': True}}]

    # I want to just set a value in the nested dict of the first item in the list
    mytest.stuff[0]['info']['test'] = False

    # However, all items in the list change
    print(mytest.stuff)
    # >>> [{'info': {'test': False}}, {'info': {'test': False}}, {'info': {'test': False}}]

This happens on both Python 2 and 3. The only way I can get around this is to not use the separate variable "inside_init_dict", and directly set the initialized dictionary:

class Tester():
    def __init__(self, stuff):

        # stuff is a list of dictionaries
        self.stuff = stuff

        # Each item in the list should be initialized to this dictionary
        for x in self.stuff:
            x['info'] = {'test': True}

if __name__ == '__main__':
    new_stuff = [{'info': {}}, {'info': {}}, {'info': {}}]
    mytest = Tester(new_stuff)

    print(mytest.stuff)
    # >>> [{'info': {'test': True}}, {'info': {'test': True}}, {'info': {'test': True}}]

    mytest.stuff[0]['info']['test'] = False

    # This is what I want
    print(mytest.stuff)
    # >>> [{'info': {'test': False}}, {'info': {'test': True}}, {'info': {'test': True}}]

What's going on here? I have tried setting the variable "inside_init_dict" in various places, like as a class variable or outside the class. The issue still occurs.

In the first example you create a single dictionary inside_init_dict outside the loop and put it in multiple places. Every element of the list gets that same inside_init_dict . What you're seeing is not other objects in a list being affected, there's just one object being shown multiple times.

In the second example:

    for x in self.stuff:
        x['info'] = {'test': True}

Now each x gets its own dictionary. They all have the same value at first, but they are different instances, like identical twins.

This happens because dicts are mutable , meaning that you can change their content without changing their identity. Here's a much simpler example of the behavior you're seeing:

my_dict = { "key" : "value" }
my_list = [ my_dict, my_dict ]
my_list[0]["key"] = "new_value"
print(my_list) # [ {"key" : "new_value"}, {"key": "new_value"} ]

Why this happens:

In the first line of this code, I create a new dictionary, {"key" : "value"} , and assign the name my_dict to it.

In the second line, I create a list, whose zeroth and first element both point to my_dict .

In the third line, I access my_dict (through my_list[0] ), and I mutate it: changing the value associated with "key" .

In the fourth line, I check the value of my_list . Both the zeroth and the first element of my_list still point to my_dict -- and we've changed my_dict . So the change is reflected in both elements of the list.

One way to fix it:

Instead pointing to the same dictionary twice, create two dictionaries that have the same value:

my_list = [ { "key" : "value" } , { "key" : "value" } ] 
my_list[0]["key"] = "new_value"
print(my_list) # [ {"key" : "new_value"}, {"key": "value"} ]

Assign the keys to different copies of the inside_init_dict dictionary instead of the same one:

    ...
    inside_init_dict = {'test': True}
    for x in self.stuff:
        x['info'] = inside_init_dict.copy()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM