Problem with objects copying each other in memory

D

dlocpuwons

Using Python 2.6.1...

I am (attempting) to make an A* search for a chess problem, but I am
running into a really annoying shared memory issue in my successor
function. Here it is stripped down to the important parts that relate
to my problem.

def successors(self):
result = []
moves = [[2, 1], [2, -1], [-2, 1], [-2, -1], [1, 2], [1, -2], [-1,
2], [-1, -2]] #possible moves for a knight

for i in moves:
temp = Knight(self.x, self.y, self.g, self.h, self.gp, self.sl)
temp.x += i[0]
temp.y += i[1]
temp.sl.append([temp.x, temp.y]) #Adds the new current state to the
visited states list
result.append(temp)
return result

The method creates a temporary Knight object, increments it to the new
position and then adds this new position to its list of visited
states. Then it returns a list of these 8 new objects. The problem
seems to be with the "result.sl.append()" line. As the method chugs
along and creates the new objects the "temp.sl" lines seems to stay in
memory, so when the method is done all the new objects have all the
new states that were made over the course of the method instead of
just their own created in the loop. For example when I try to get
successors for a piece that is initially at (2,2) with no previously
visited states, the method prints this out for the sl (state list)
value

[[2, 2], [4, 3], [4, 1], [0, 3], [0, 1], [3, 4], [3, 0], [1, 4], [1,
0]]
[[2, 2], [4, 3], [4, 1], [0, 3], [0, 1], [3, 4], [3, 0], [1, 4], [1,
0]]
[[2, 2], [4, 3], [4, 1], [0, 3], [0, 1], [3, 4], [3, 0], [1, 4], [1,
0]]
[[2, 2], [4, 3], [4, 1], [0, 3], [0, 1], [3, 4], [3, 0], [1, 4], [1,
0]]
[[2, 2], [4, 3], [4, 1], [0, 3], [0, 1], [3, 4], [3, 0], [1, 4], [1,
0]]
[[2, 2], [4, 3], [4, 1], [0, 3], [0, 1], [3, 4], [3, 0], [1, 4], [1,
0]]
[[2, 2], [4, 3], [4, 1], [0, 3], [0, 1], [3, 4], [3, 0], [1, 4], [1,
0]]
[[2, 2], [4, 3], [4, 1], [0, 3], [0, 1], [3, 4], [3, 0], [1, 4], [1,
0]]

but what it should print out is

[[2, 2], [4, 3]]
[[2, 2], [4, 1]]
[[2, 2], [0, 3]]
[[2, 2], [0, 1]]
[[2, 2], [3, 4]]
[[2, 2], [3, 0]]
[[2, 2], [1, 4]]
[[2, 2], [1, 0]]

It sort of seems like python is trying to be too smart and is trying
to keep things in memory. Is there anyway to work around this?
 
C

Chris Rebert

Using Python 2.6.1...

I am (attempting) to make an A* search for a chess problem, but I am
running into a really annoying shared memory issue in my successor
function. Here it is stripped down to the important parts that relate
to my problem.

def successors(self):
result = []
moves = [[2, 1], [2, -1], [-2, 1], [-2, -1], [1, 2], [1, -2], [-1,
2], [-1, -2]] #possible moves for a knight

for i in moves:
temp = Knight(self.x, self.y, self.g, self.h, self.gp, self.sl)
temp.x += i[0]
temp.y += i[1]
temp.sl.append([temp.x, temp.y]) #Adds the new current state to the
visited states list
result.append(temp)
return result

The method creates a temporary Knight object, increments it to the new
position and then adds this new position to its list of visited
states. Then it returns a list of these 8 new objects. The problem
seems to be with the "result.sl.append()" line. As the method chugs
along and creates the new objects the "temp.sl" lines seems to stay in
memory, so when the method is done all the new objects have all the
new states that were made over the course of the method instead of
just their own created in the loop. For example when I try to get
successors for a piece that is initially at (2,2) with no previously
visited states, the method prints this out for the sl (state list)
value

Just a guess based on the behavior you described, but do you by chance
have the Knight class defined as something like this?:

class Knight(whatever):
sl = []

If so, that is not the proper way to create an instance variable in
Python. You've instead created a class variable, which is shared by
all instances of the class.

class Knight(whatever):
def __init__(self, some, args, here):
self.sl = []

If not, do you by chance define Knight as:

class Knight(whatever):
def __init__(self, some, args, here, sl=[]):
self.sl = sl

If so, you've fallen victim to the Important Warning on
http://docs.python.org/tutorial/controlflow.html#default-argument-values
under "Default Argument Values". The correct way to code it is:

class Knight(whatever):
def __init__(self, some, args, here, sl=None):
if sl is None: sl = []
self.sl = sl

Cheers,
Chris
 
R

Rhodri James

Thanks, that did it! Why is that the case though? Or rather, why do the
assignments to temp.x and temp.y not effect the self.x and self.y? How
come I only run into the problem with the list?

Variable names and assignment don't work the same way in Python as they
do in C (say). What assignment does is to attach the name on the left
to the object on the right. Obviously this is oversimplifying, since
"the name on the left" could be a list or dictionary element or the like,
but the principle holds: variables aren't objects themselves, they're
just references to objects.

For your ordinary everyday integers, this is arranged to be no different
from normal. Suppose we have

x = 5
y = x
x = x + 1
print y

Then we start by creating an integer object with the value 5 and labelling
it "x". Then we pick up that object and label it 'y' as well. The next
line causes us to create an integer object with the value 1, pick up the
object we called 'x', and tell that object to add the '1' object to itself.
That results in creating a brand new object (with the value 6), which we
then label 'x'. The original '5' object (still labelled 'y') hasn't been
changed by this, because Python does arithmetic by creating new integer
objects.

Lists operate the same way for concatenation and repetition.

a = [1, 2, 3]
b = [4, 5, 6]
c = a
a = a + b
print c

The "a = a + b" line creates a new list consisting of the elements of
the old list 'a' followed by the elements of the old list 'b', without
changing either of those lists in the slightest, and then calls this
new list 'a'. The old list is still hanging around, and still has the
label 'c' attached to it.

The differences from your expectations arise because lists are mutable,
and integers aren't. In other words we can change the contents of a
list "in place" without creating a new list.

a = [1, 2, 3]
b = a
a.append(4)
print b

Here, the list has both labels 'a' and 'b' attached to it. When we
call a.append, it doesn't create a new list or anything like that,
it just makes the existing list larger and tacks the new value on
the end. Because it's still the same list as before, it's still
got both names attached to it, so when you print 'b' out you see
the changed list [1, 2, 3, 4].
 
B

Bruno Desthuilliers

Cameron Pulsford a écrit :
Thanks, that did it! Why is that the case though? Or rather, why do the
assignments to temp.x and temp.y not effect the self.x and self.y? How
come I only run into the problem with the list?


Because there's a huge difference between binding an object to a name
and mutating an object ?

First point: Python's "assignment" is really a binding of a name and an
object _reference_ in a given namespace. Think of namespaces as
name=>object_ref dictionnaries. This implies that "assignement" never
copies anything. So when you do:
>>> list1 = []
>>> list2 = list1

the second line actually creates *another* name=>object pair referencing
the *same* list object. IOW, list1 and list2 are two named references to
a single object:
True

So whether you access it thru name 'list1' or 'list2', if you mutate the
object (like append/remove/replace an element of the list), you'll see
the result thru the other name as well:
>>> list1.append('foo')
>>> list2.append('bar')
>>> list1 ['foo', 'bar']
>>> list2 ['foo', 'bar']
>>>

Note FWIW that list subscripting (somelist[x] = y) is really a method
call (somelist.__setitem__(x, y)) in disguise, so the same reasonning
applies.

Now *rebinding* a name is a different thing. It makes the name refer to
another object, but has no impact on other name=>object bindings
refering to the previously bound object, ie:

Now list2 points to a newly created list object. This doesn't impact
list1 of course:
>>> list1 ['foo', 'bar']
>>> list1 is list2 False
>>> id(list1) == id(list2) False
>>>


(snip)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,190
Members
46,736
Latest member
zacharyharris

Latest Threads

Top