4

I have a list of objects of a class. When I change the object that I use in the append function, the list also changes. Why is that? I am coming from C++ so this is quite strange.

I have the following code:

    class state:

    def __init__(self):
            self.x=list([])
            self.possibleChests=list([])
            self.visitedChests=list([])

    def __str__(self):
            print "x ",
            print self.x
            print "possibleChests ",
            print self.possibleChests
            print "visitedChests ",
            print self.visitedChests
            return ""


    def addKey(self,key):
            self.x.append(key)

    def __eq__(self,other):
            if isinstance(other,self.__class__):
                    return self.__dict__==other.__dict__
            else:
                    return False


    current_state=state()

    current_state.addKey(4)
    current_state.possibleChests.extend([1,2,4])
    current_state.visitedChests.append(5)

    visitedStates=list([])

    visitedStates.append(current_state)

    current_state.addKey(5)


    if(current_state in visitedStates):
            print "Got ya!!"

    else:
            print "Not in list!!"

And I get the output:

    Got ya!!

I have changed the current_state object so it should not be in the list.

user2105632
  • 751
  • 1
  • 7
  • 12
  • 1
    You don't put a *copy* into the list - it's a reference to *the exact same object*. – jonrsharpe Jun 21 '14 at 20:52
  • Is it different from the prompt? Because on the prompt, when I change the variable, the list doesn't change. How can I add by value in the list? – user2105632 Jun 21 '14 at 20:54
  • It's the same in the prompt; without seeing the exact code I can't explain why you think it's different. You can't "add by value" - it's all references in Python. Names like `current_state` and collections like `list` just provide references to the underlying objects. If you want a separate version of the object, define a method to copy it - see https://docs.python.org/2/library/copy.html – jonrsharpe Jun 21 '14 at 20:57
  • You might find this useful: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables – jonrsharpe Jun 21 '14 at 20:59
  • On the prompt I did the following:>>> b=[] >>> a=5 >>> b.append(a) >>> a=6 >>> b [5] – user2105632 Jun 21 '14 at 21:01
  • Then the difference is because integers are immutable, so `a = 6` creates a new integer object (not really in that case, but let's not worry about it now) and assigns that to `a`, whereas appending to a list modifies the existing object. See also [this question](http://stackoverflow.com/q/986006/3001761). – jonrsharpe Jun 21 '14 at 21:04
  • Another explanation of how Python names and values work: http://nedbatchelder.com/text/names.html – Ned Batchelder Jun 21 '14 at 21:12
  • @NedBatchelder bookmarked, despite shameless self-promotion! – jonrsharpe Jun 21 '14 at 21:24
  • what's that silly `list([])`? Either write `list()` or `[]`, not both. – Daniel Jun 21 '14 at 21:55

2 Answers2

3

Your state object is mutable. When you call current_state.addKey(5) you modify current_state but all pointers to it still point to it. And in python list only contain pointers to objects. You have same thing with lists which are mutable :

l1 = [ 1, 2]
l2 = l1
l1.append(3)
l2
=> shows [1, 2, 3]

You cannot have same thing with strings (for example) because they are immutable. You cannot modify them, but only point to another string

s1 = "abc"
s2 = s1
s1 = "def"
s2
=> shows "abc"

This is a common source of mistake when you create an object outside of a loop and in the loop modify it and add it to a list : at the end of the loop you have a list containing n times the same object with last value. And this is not specific to python but can be observed in Java and even in C or C++ if you only store pointers to a mutable object.

C++ example (it is not good C++ but the minimum to exhibits the problem)

class pair {
    public:
        int key;
        int val;
}

pair* map[5];

pair p;

for (i=0; i<5; i++) {
    p.key = i;
    p.val = 2 * i;
    map[i] = &p; // only store pointer
}

At the end of the loop map contains 5 pointers to the same pair with key=4 and val=8

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
1

When I change the object that I use in the append function, the list also changes.

Add a copy of the object or a new object rather than sharing the same object.

Operations like = and append share a reference and do not create new objects. You can create a new distinct state by calling state() again. You can create a copy by using the copy module:

from copy import deepcopy

visitedStates.append(deepcopy(current_state))
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485