Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python deepcopy, dictionary value in object changes

I have a python code snippet here:

import copy

class Foo(object):
    bar = dict()

    def __init__(self, bar):
        self.bar = bar

    def loop(self):
        backup = copy.deepcopy(self)
        backup.bar[1] = "1"
        for i in range(0, 10):
            print "BACKUP BAR IS: ", backup.bar
            self.bar[1] = "42"
            self.bar = backup.bar

a = Foo({1:"0"})
a.loop()

This prints out BACKUP BAR IS: {1: '1'} the first two times, but then it starts to print out BACKUP BAR IS: {1: '42'} the next eight times. Can anyone tell my how and why this is happening? Doesn't deepcopy completely create a new instance of self? How does the value of backup bar change when we only change the value of "self", which is "a", in this case?

Edit: some have noted that the line self.bar = backup.bar causes the two dicts to point to each other. But, if we do:

 a = {1:1}
 b = {1:2}
 c = {1:3}
 a = b
 a = c

If a and b indeed point to the same dictionary, then all three values of a, b, c should be equal to c. But instead, a = c = {1:3}, b = {1:2}. So changing the reference of the left hand side doesn't change the reference of the right hand side. Similarly, how would backup.bar be changed if we're only changing self.bar?

like image 561
lu6cifer Avatar asked Mar 19 '23 03:03

lu6cifer


1 Answers

Setting self.bar = backup.bar does not mutate the dictionary that self.bar is pointing to, nor does it create a copy of backup.bar to assign to self.bar. Rather, it changes the pointer of self.bar to refer to the dictionary at backup.bar, so that they now refer to the same object. Take a look at this demonstration:

In [48]: a = Foo({1: "0"})

In [49]: backup = copy.deepcopy(a)

In [50]: a.bar = backup.bar

In [51]: id(backup.bar)
Out[51]: 140428501511816

In [52]: id(a.bar)  # Same object! Alternatively, `a.bar is backup.bar` -> True
Out[52]: 140428501511816

In [53]: backup.bar[1] = "42"

In [54]: a.bar
Out[54]: {1: '42'}

The discrepancy with iteration is explained by @alfasin.

With regard to your question edit, you never change the dictionary that b points to. You're simply changing where a is pointing to twice. a = c does not mean change b = c. It simply means a was pointing to b, now it's pointing to c.

Edit drawings because why not.

a ---> {1: 1}
b ---> {1: 2}
c ---> {1: 3}

After a = b:

a --------   # So long, {1: 1}
         |
         v
b ---> {1: 2}
c ---> {1: 3}

After a = c, i.e., a points to what c points to:

a ---------------
                |
                |
b ---> {1: 2}   |
c ---> {1: 3} <-|
like image 171
jayelm Avatar answered Mar 26 '23 02:03

jayelm