Python variable linked to attribute?

Hello,

I normally don’t have any issues with python, but I just stumbled upon something… strange.

Here’s the code I used:


def winSwap(ob1,ob2):
    pos1 = objs[ob1].worldPosition
    pos2 = objs[ob2].worldPosition
    print(pos1,pos2)
    
    objs[ob1].worldPosition = pos2
    objs[ob2].worldPosition = pos1
    print(pos1,pos2)

When this is run, where ob1 and ob2 are objects in the scene list “objs”, the terminal output is like so:


Blender Game Engine Started
<Vector (0.0000, -3.0000, 0.0000)> <Vector (10.0000, -3.0000, 0.0000)>
<Vector (10.0000, -3.0000, 0.0000)> <Vector (10.0000, -3.0000, 0.0000)>
Blender Game Engine Finished

notice how I printed the same variables, pos1 and pos2, at different times without changing them myself and yet it changed (here pos1 changed to the position of the second object, I suppose because it was first moved to the new location, so when I try to change the position of the second object it just stays where it is).

My question: what. And perhaps why. Maybe how? Yes, probably.

I know I could do a workaround where it gets the difference in xyz position and just increment each one by the (positive or negative) difference. Buuttt… this like fundamentally disrupts my knowledge of how python works. Or at least in the game engine. How is it that the variable is physically connected to the property of the object that is the xyz position?

Thanks for any help…

====

slight after thought, I tried the below (which worked), but here the value diff does not change as the objects themselves move (which is what I would expect). Even with the first code, posted above the '='s, if I set pos1 and pos2 to a and b or whatever, a and b still updated as pos1 and pos2 did.

def winSwap(ob1,ob2):
    pos1 = objs[ob1].worldPosition
    pos2 = objs[ob2].worldPosition
    diff = pos1-pos2
    print(diff)
    objs[ob1].worldPosition-=diff
    objs[ob2].worldPosition+=diff
    print(diff)

(output)

<Vector (-10.0000, 0.0000, 0.0000)>
<Vector (-10.0000, 0.0000, 0.0000)>

notice here it stays the same.

Again, thanks for any help… I’d like to know if there are other such properties that will always ‘update’ or if there is some like proper way of programming to avoid variables changing implicitly. I’m sure this was a source of unknown error in the past for me.

I might not be of any help since I don’t know python and might not understand what you’re looking for, but what happens when you add .copy() to your script like this


def winSwap(ob1,ob2):
     pos1 = objs[ob1].worldPosition.copy()    
     pos2 = objs[ob2].worldPosition.copy()     
     print(pos1,pos2)    
      
     objs[ob1].worldPosition = pos2
     objs[ob2].worldPosition = pos1
     print(pos1,pos2)

yup. You very well may know more about the blenders game engine than me from that :wink: I still don’t understand why this is practically advantageous to give an updating variable, and furthermore how have I not run into it before… but yes, thanks a bunch :slight_smile:

Okay, so here’s what (I think) is going on.

The world position of objects are actually objects of a Python class - the mathutils.Vector class. This being the case, when you set the worldPosition property, you’re setting it to a pointer to an object, most likely another Vector. So, obj1.worldPosition doesn’t hold the world position of obj1, but a pointer to the world position of obj1. Assigning obj1.worldPosition to a new variable makes the variable a reference to that position.

This is the same for orientation, as well - it’s an instance of the Matrix class.

What this boils down to is this example:



position = obj.worldPosition

position.x -= 10


That should (if I know what I’m talking about) move the object “obj” by -10 Blender Units on the X-axis, because position is a pointer to the object’s worldPosition Vector. If you want to make a copy of it, do so with the copy() function (a function present in Vectors):



position = obj.worldPosition.copy()

position.x -= 10

#obj.worldPosition remains unchanged


This is present in all cases of objects and classes. For example, say you make a class of your own and want to change a value in it. Say you wanted to make a new cup, and give it a variable, amount, indicating how much of whatever substance is in it. In the example, you make a new cup object, then create another variable because you want to refer to it differently.



class MyCup():

     def __init__(self):

          self.amount = 10

cup1 = MyCup() # Make a new MyCup object, and assign the reference to cup1

cup2 = cup1       # Make a reference to the object in cup1, and assign that to cup2

cup2.amount = 1 #cup1's amount is now also 1


As you can see, both cup1 and cup2 refer to the same object, and so changes to one affect both. If you want to break the link, so to speak, you should make a new instance (like you do with copy() above):



class MyCup():

     def __init__(self):

          self.amount = 10

cup1 = MyCup() # Make a new MyCup object, and assign the reference to cup1

cup2 = MyCup()  # Make another new MyCup object, and assign the reference to cup2

cup2.amount = 1 #cup1's amount is still 10


The copy() function creates a new Vector (or whatever class that supports it) and fills the properties the same as the ‘source Vector’.

EDIT: Oh, but Python’s not stupid - if you assign two variables (obj1.worldPosition and obj2.worldPosition) to pointers of the same object (a Vector) like you originally did above, it would seem that the BGE automatically will make a new Vector to call its own, so to speak, so both objects will have their own Vectors for world position (they won’t end up being ‘parented’ or following each other around, for example).

That block alone made it make perfect sense, I think I understand better what’s going on then. I’ve always considered that worldPosition gets the position (such that you set the value of a variable to the same value of another), not that worldPosition was itself the state of where the objects exists.

Thanks for the clarification, I can sleep well :slight_smile: (I’m still amazed I never had this issue before, since using worldPosition is so ubiquitous)

EDIT: that also cleanly explained why my alternative method of using the difference works, since it’s setting the difference vector based on the instant positions of the two other objects, not setting itself to one or the other like the 1:1 equation

What SolarLune said is essentially correct.

Although, I would phrase things a little differently, and (in my opinion) more correctly:

It’s a good idea to exclude the word “pointer”, because Python doesn’t really have a concept of an explicit pointer. We have “implicit pointers”, which are more commonly known as references, and that would be the proper terminology. In Python, every variable is a reference to an object in memory.

And that brings me to this quote: “The world position of objects are actually objects of a Python class”. This statement implies that there are some things in Python which are not objects, and that contradicts the whole “everything is an object” mantra which Python follows.

As far as default Python assignment behavior is concerned, everything is an object, which is either mutable (assignment gives new reference to old object) or immutable (assignment gives new reference to a newly created copy of old object).

I’m pretty sure that this behavior is dictated by an overloaded assignment operator, and not Python itself (continuing from your example):


cup2 = cup1

You now have two references to the first instance of MyCup - the second instance, previously referenced by cup2, is relegated to the garbage collector, and removed from memory soon after.

So, it’s not the cleverness of Python, but rather the convenience of the mathutils library.

Indeed, reference / pointer misuse is something even i have been guilty of; using c++ originally. If you aren’t sure how the python layer of the bge works, you’d be wise to do some reading. Essentially, c++ provides an object (class instance) for each game object. It maintains that reference, and has methods for setting and reading attributes in both directions. If your unsure about mute able and immutable types, have a look at functions. Creating a simple function to modify an argument but without returning it can be a good lesson. For the ‘python mantra’ which can be missed by a starting programmer, have a look at type() and meta classes. Interesting stuff. Oh and Goran? I hope the you haven’t looked into the bge source; there is are like 4 different math libs :slight_smile:

I think the important terms are:

KX_GameObject.worldPosition and other attributes (such as velocities, orientations) return references since API 2.5.
The Pre-2.5 API returned copies only.

There are a few benefits on returning references. As we see it is quite easy to produce confusing side-effects.

I trapped in the side-effects multiple time. And I will do ion the future. It is quite hard to identify this as reason for issues as it is not that obvious. So keep in mind it can become handy to add copy() after reading a vector ;).

Thanks for all the pointers everyone, it’s quite useful.

@agoose77: Any chance you have a link to where I might read up on how the python layer works? (quick search didn’t really help, probably not using the best keywords) I wouldn’t understand the source code but documentation could be quite useful.

:slight_smile: