|
From: <gp...@ri...> - 2004-08-22 19:59:46
|
All,
I have a visual python program whose behavior changes
when I alternately comment and uncomment these two lines:
s.v += (force / s.mass) * dt
s.v = s.v + (force / s.mass) * dt
where s is a sphere instance and... well, see below.
I thought this was odd, so I asked Jonathan to take a look.
He did, and:
Gotme! (as in Gotcha!)
Jonathan's reply to me is reproduced below. There are some
good lessons here. I think there are three: 1.) the reference
vs. value feature, 2.) a += b is not entirely equivalent to a = a+b,
3.) defualt arguments in function defs are built into the function
object when the function is defined, not when it is called.
The refernce to MakeMass() is a reference to my program. I *think*
Jonathan's explanation is clear enough on its own without having to know
exactly what's inside MakeMass.
regards, with thanks to J.B.,
-gary
---------------------------- Original Message ----------------------------
Subject: Re: incrementing vectors "problem"
From: "Jonathan Brandmeyer" <jbr...@ea...>
Date: Wed, August 18, 2004 5:09 pm
To: gp...@ri...
--------------------------------------------------------------------------
>On Wed, 2004-08-18 at 09:28, gp...@ri... wrote:
> This is a work in progress. There must be a coding problem somewhere
>(there are a number of changes in the works) but I'm stuck at this
spot. >If you can just run it and let me know if they are the same or
different >for you, I'll have some idea about what I should do next.
(I'm not asking >you to debug my code!)
I was sufficiently perplexed that I did debug your code. You have been
burned by reference vs. value semantics.
Consider the following code:
from visual import *
spheres = []
velocity = vector(0,0,0)
spheres.append( sphere( v=velocity))
spheres.append( sphere( v=velocity))
spheres.append( sphere( v=velocity))
spheres.append( sphere( v=velocity))
twice = 2
while twice:
print "before iteration:"
for i in spheres:
print i.v
ctr = 0
for i in spheres:
print ctr
ctr += 1
print "before:", i.v
i.v += vector(.01, .01, .01)
# i.v = i.v + vector(.01, .01, .01)
print "after", i.v
twice -= 1
Run it, and be surprised. The problem is that when each sphere is
created, its 'v' attribute is a reference to the single vector pointed to
by 'velocity'. So, when the loop runs with += expressions, each of them
is changing the single global vector 'velocity', but when it is run with
'x = x + y' expressions, on the first iteration, each 'v' attribute is
reassigned to a new, unique vector: the returned result of the
addition.
What is the fix? Whenever you want a true copy, you can invoke the "copy
constructor" for a vector to break the reference cycle:
velocity = vector(0,0,0)
v = vector(velocity)
Note that all of the visual objects' vector attributes underlying "set
functions" do essentially the same thing.
Actually, there is one additional piece of information that is specific to
your code. In your case, the common vector was the default value for the
'velocity' argument in the MakeMass() function. When the
interpreter passes the closing line of the function definition, it creates
a callable object, named "MakeMass", and that object has a single copy of
any default arguments within it. Every time MakeMass's __call__() member
function is invoked without one of the arguments for which there is a
default, that parameter is replaced with a reference to the single
instance of the default value that was created when MakeMass was created.
I know its a damned subtle problem, but those are Python's semantics.
HTH,
-Jonathan
|