0

I have an infuriating problem with the code below. I have some objects which each hold internally a list of other objects. The code creates a new instance of a 'two' object, adds it into the 'three' object collection, and then creates a random number of 'one' objects and adds them into the newly created 2 object. When I iterate through three's collection of two objects, I expect to see that each two object has a random number of one objects, but in fact I see that all two objects have the same number of one objects.

What on earth am I doing wrong, or not understanding?

from typing import List
from random import randrange

class one:
    __name: str = None
    def __init__(self, name: str):
        self.__name = name
    def get_name(self):
        return self.__name

class two:
    __name:str = None
    __ones: List[one] = []

    def __init__(self, name: str):
        self.__name = name
    def get_name(self):
        return self.__name
    def num_ones(self) -> int:
        if not self.__ones:
            return 0
        return len(self.__ones)
    def __eq__(self,other):
        if not other:
            return False
        if not isinstance(other, self.__class__):
            return False
        if self.get_name() == other.get_name():
            return True
        return False
    def __str__(self):
        ret = self.get_name()
        ret += " ones:"
        ret += str(self.num_ones())
        return ret

    def add_one(self, name: str):
        for o in self.__ones:
            if o.get_name() == name:
                return o
        foo = one(name)
        self.__ones.append(foo)

class three:
    __twos: List[two] = []
    def __init__(self):
        pass
    def add_two(self, t:two):
        for i in self.__twos:
            if i == t:
                return o
        self.__twos.append(t)
    def __str__(self):
        ret = "twos:"
        ret += str(len(self.__twos))
        return ret

athree = three()

for i in range(1,60):
    #create a new two object
    atwo = two("two0" + str(i))
    #print the two object, it should contains zero one objects
    #because it's only just been created
    #but instead it contains the same as all other instances of two objects
    print(str(atwo))
    #add newly created object into a collection of two objects
    athree.add_two(atwo)
    #now create a random number of one objects and 
    #add them into our newly created two object
    #so we expect three to contains 60 two objects each
    #with a random number of one objects in them.
    rand = randrange(20)
    for o in range(1,rand):
        aone = atwo.add_one("one0" + str(o))

example output

..
two044 ones:17
two045 ones:17
two046 ones:17
..

here we expected to see that the number of 'ones' in each 'two' is random, but all sixty instances of two contains 17 ones!?

EDIT:

following the answer, which is that in Python properties declared in the body of the class are static until assigned by a method (usually the constructor), here is the working code :

from typing import List
from random import randrange

class one:
    __name: str = None
    def __init__(self, name: str):
        self.__name = name
    def get_name(self):
        return self.__name

class two:
    __name = None
    __ones: List[one] = None
    def __init__(self, name: str):
        self.__name = name
        self.__ones: List[one] = []
    def get_name(self):
        return self.__name
    def num_ones(self) -> int:
        if not self.__ones:
            return 0
        return len(self.__ones)
    def __eq__(self,other):
        if not other:
            return False
        if not isinstance(other, self.__class__):
            return False
        if self.get_name() == other.get_name():
            return True
        return False
    def __str__(self):
        ret = self.get_name()
        ret += " ones:"
        ret += str(self.num_ones())
        return ret

    def add_one(self, name: str):
        for o in self.__ones:
            if o.get_name() == name:
                return o
        foo = one(name)
        self.__ones.append(foo)

class three:
    __twos: List[two] = None
    def __init__(self):
        self.__twos: List[two] = []
    def add_two(self, t:two):
        for i in self.__twos:
            if i == t:
                return o
        self.__twos.append(t)
    def __str__(self):
        ret = "twos:"
        ret += str(len(self.__twos))
        return ret

athree = three()

for i in range(1,60):
    #create a new two object
    atwo = two("two0" + str(i))
    #add newly created object into a collection of two objects
    athree.add_two(atwo)
    #now create a random number of one objects and 
    #add them into our newly created two object
    #so we expect three to contains 60 two objects each
    #with a random number of one objects in them.
    rand = randrange(20)
    for o in range(1,rand):
        aone = atwo.add_one("one0" + str(o))
    print(str(atwo))
Richard
  • 1,070
  • 9
  • 22
  • 2
    You need to add the line `__ones: List[one] = []` inside the `__init__` method, so that each `two` instance starts with their own `__ones` list. By placing a variable outside any methods of a class, it is declared as a class variable and shared across all of its instances. – jfaccioni Mar 24 '21 at 11:43
  • 1
    Because you are using a class variable. i.e. static. I assume you are coming from a language like Java. Stop writing python like that. Stop using getters and setters, and don't use double-underscore name mangling like that. – juanpa.arrivillaga Mar 24 '21 at 11:45
  • @buran potentially yes. As you can probably tell I'm a java dev. In java you can see a list of all object properties at the top of the class which is neat. I suppose in Python you can do the same, you just must remember to look in the constructor instead. – Richard Mar 24 '21 at 12:00
  • @buran however, after moving the declaration into the constructor, now I'm getting zero one instances in every two instance. – Richard Mar 24 '21 at 12:02
  • @Richard, there is difference between class attributes and instance attributes. See https://stackoverflow.com/questions/207000/what-is-the-difference-between-class-and-instance-attributes in addition to above link about mutable class attributes – buran Mar 24 '21 at 12:07
  • re: earlier comment about getting zero one instances.. of course I am, this is correct behaviour. – Richard Mar 24 '21 at 12:14

0 Answers0