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 |