Why lists seem not specific to an object of a class?

I though that i could instance an object and get a list as property. But i see that when i modifiy the list for the object P1 , it changes also the list of the object P0 . Why ?

import bge

scene = bge.logic.getCurrentScene()
cont = bge.logic.getCurrentController()
own = cont.owner


key = cont.sensors['key'].positive

graph = ['11','22']


class Person:
  def __init__(self, name, age, graph ):
    self.name = name
    self.age = age
    self.graph = graph

  def addz(self):
    self.graph.append("5")
    
    
if key :
    print(graph)
    p2 = Person("Hector", 13, graph)
    p1 = Person("John", 36, graph)

    p1.addz()

    print('------------')
    print(p1.graph)
    print('------------')
    print(p2.graph)  
    

Why p1.graph and p2.graph output the same ?

Because you’re supplying the same list to both classes. More accuractly, you’re supplying both classes the same reference (think like a RAM pointer) to the same list.

If you want different lists, then you’ll need to copy it:

from copy import deepcopy
orig_list = [1,2,3]
new_list = deepcopy(orig_list)

Or, this works most of the time as well, presuming it’s a list of primatives:

orig_list = [0,1,2,3]
new_list = orig_list[:]

Lists are stored/passed as references, so:

my_list = [1,2,3]
my_list.append(4)
print(my_list) #[1,2,3,4]

Is the same as:

my_list = [1,2,3]
a_list = my_list
a_list.append(4)
print(my_list) #[1,2,3,4]

Is exactly the same as:

def do_thing(a_list):
    a_list.append(4)

my_list = [1,2,3]
do_thing(my_list)
print(my_list) #[1,2,3,4]

Because the variable a_list is a pointer to the original list, so modifications to a_list affect my_list and visa versa.

The solution: copy the list first

thx you both.

that’s weird :dizzy_face:

But how could I use this with class ? The idea is to avoid making copies for each new object

Like people said, most arguments are passed by reference in Python, except for int, str, and bool.

I’d like to add that for data types that may be passed by reference, you can adopt different strategies to dedup them:

  1. Use a factory to get your instances:

    def getNewGraph():
        return ['11', '22']
    
    thingThatNeedGraph(getNewGraph())
    

    In this case, getNewGraph will always return a new list each time it is executed.

    The interesting property with factories is that you can make the function that returns your instance configurable: Add function parameters, and process them to return the new object.

  2. What people mentioned here: Copy your original data.

    initialGraph = ['11', '22']
    
    thingThatNeedGraph(initialGraph.copy())
    

    This approach is similar to the first one, except that you leak some implementation details about how to get a new graph (which in this case involves doing a list.copy())

Both work, pick your poison.

What do you mean here?

class Person:
  def __init__(self, name, age, graph ):
    self.name = name
    self.age = age
    self.graph = graph[:]

Either you’re using the same list, which means they all have the same contents, or you’re using different lists that have different contents. You can’t avoid making copies if you want to have different contents…

thx to all.

Just learning python with BGE so i might ask noobs questions. For me, it was obvious that i soon as i made a : " listB = listA " it’s because i need a copy of it. But here, it’s like simply giving the thing many names (which might be usefull in case if the name matters for some reference matching in a script).

I give to TheDave the reward because he had latest answer : So people will read the whole thread (but you all helped me! ) :nerd_face:

How can I associate a game Object with a class Object ?

I see an answer of Monster here , but it looks weird to associate the cO with a property of a gO .

Would not it be more elegant to associate the cO to a gO within the init_ in the class definition ?

class Person:
  def __init__(self, name ):
    self  = scene.objects[name]

p1 = Person('crazy_npc')

######  then use it like any gO
p1.dothisBGEfunnyfunction() 

I know this is not possible like that (well not tested yet) :no_mouth:, but something similar

Actually, your code isn’t far from workable like that. In SpellShips, I have different classes that represent players/ai/towers and each of them has a property called “object” that I set to the game object so I can manipulate its position and activate actions on it.

Take this script for example. It’s on an empty that I set its position to where the player is looking.

import bge.logic as GameLogic

cont = GameLogic.getCurrentController()
own = cont.owner
sen = cont.sensors
act = cont.actuators

own.position = GameLogic.player_focus_pos

if GameLogic.player_focus_obj == None:
	GameLogic.player_focus_obj = own

Note the last line where I’m setting a property in bge.logic to a reference to a game object.

But how could I use this with class ? The idea is to avoid making copies for each new object

I think you’re assuming that there’s something wrong with the class. There’s not. The class is working fine. However this:

    p2 = Person("Hector", 13, graph)
    p1 = Person("John", 36, graph)

Is what is resulting in the two sharing the list. Effectively you need to change it to:

    p2 = Person("Hector", 13, graph.copy())
    p1 = Person("John", 36, graph.copy())

I’d prefer copying explicity outside the class (as per example above) rather than inside the class (ie using the solution TheDave suggested. This is because there may be cases where you want them to share graphs or where the graph is too expensive to copy.

It’s worth noting that Rust (a different programming language) would have not compiled the code above because it would detect that both instances have mutable references to the same data, which can result in odd behaviours like this. To resolve it you’d either have to give them read only access to the graph, clone the graph, or pack it in a runtime-borrow-checked-object and handle when the data can mutate yourself. While you do end up fighting the rust compiler a fair bit, it does make it clear where this sort of problem is happening.


For me, it was obvious that i soon as i made a : " listB = listA " it’s because i need a copy of it.

Python is a high level language and tries to hide thungs from you. As was hinted at before, bools/ints/strings and lists/objects/dictionaries are different:

a = 1
b = a
b = 3
print(a) #1 -- b is a copy of a

a = [1]
b = a
b[0] = 3
print(a) #[3] -- b is a reference to a

Python could make copies of lists each time, but:

  1. Performance. If you have 3Gb of data in a list, b = a would stall the computer for ages as it copied, and you’d run out of ram in short order.
  2. it’s hard to tell where to stop copying. If you have a dictionary of lists of integers, do you duplicate the whole lot? Or do you copy only the dictionary and reference the lists?
  3. Often actually want shared references in OOP languages! Imagine if objects were copied each time:
objects_in_scene = scene.objects
player = objects_in_scene['player']
player.worldPosition.x += 1

Line 1 would create a copy of all the objects in the scene
Line 2 would create another copy of the player
Line 3 would move the copy of the copy of the player
And BGE would not know about these copies of the objects, so they wouldn’t be drawn at all!


For referencing game objects from code, there are two patterns I follow depending on which I feel fits better:

  1. RAII: create the object inside the initilizer
  2. Composition: pass the object in

RAII example:

class Robot:
    def __init__(self, scene):
        self.obj = scene.addObject('Robot')

    def __del__(self):
        self.obj.endObject()

When you create a robot in python, it creates a new one in the scene! When you delete the object (and don’t have reference cycles) it deletes the one in the scene!

Composition Example:

class Robot:
    def __init__(self, obj):
        self.obj = obj


#elsewhere
obj = scene.addObject('Robot')
#or
obj = scene.objects['Robot']
my_robot = Robot(obj)

RAII is nice for thing like bullets which tend to be spawned from in python. This is because you can just call Bullet(scene, position) and it creates a bullet for you. Composition is nice for multi-object entites such as players or cars that are constructed externally in blender because you can go:

class Wheel:
    def __init__(self, obj):
        self.obj = obj

class car:
    def __init__(self, car_body):
        self.body = car_body
        self.wheels = [Wheel(o) for o in car_body.children if 'Wheel' in o]

Often you can use a combination, for example:

class SomeCar:
    def __init__(self, scene):
        self.body = scene.addObject('SomeCar')
        self.wheels = [Wheel(o) for o in self.body if 'Wheel' in o]

    def __del__(self):
        self.body.endObject

Here calling `Car(scene) will create a car (RAII style), but the car ‘composes’ it’s wheels from existing objects.

1 Like