Search code examples
pythoninstance-variablespython-class

Why Instance variables reference the same object in this code?


I want to implement the move method in Shape class in has-a relation(So Shape class does not inherit CPoint class), but have a problem in setting class variables.

The given code is:

class CPoint:
    def __init__(self, x = 0, y = 0):
        self.__x = x
        self.__y = y
    
    def __str__(self):
        return f"pos({self.__x},{self.__y})"
    
    def get_x(self):
        return self.__x
    
    def set_x(self, new_x):
        self.__x = new_x
    
    def get_y(self):
        return self.__y
    
    def set_y(self, new_y):
        self.__y = new_y
    
    def move(self, a, b):
        self.__x += a
        self.__y += b
        return CPoint(self.__x,self.__y)
    

class Shape:
    def __init__(self, color = "yellow", filled = True, pos = CPoint()): 
        #So each pos must reference the different CPoint() instance 
        self.__color = color
        self.__filled = filled
        self.__pos = pos
    
    def __str__(self):
        return f"{self.__pos}({self.__color},{self.__filled})"
    
    def move(self, a, b):
        self.__pos.move(a,b)
        
        if type(self) == Shape:
            return f"{self}"
        else:
            return f"{self.__pos}{self}"


def main():
    a = Shape()
    b = Shape("red")
    a.move(2,3)
    print(b.move(4,5))
    
main()

the result is:

pos(0,0)(yellow,True)
pos(0,0)(red,True)
pos(2,3)(yellow,True)
pos(6,8)(red,True)

and the result should be like:

pos(0,0)(yellow,True)
pos(0,0)(red,True)
pos(2,3)(yellow,True)
pos(4,5)(red,True)

And I executed the code on the python tutor, and the visualization of the code is like this: python tutor visualization

So Shape() and Shape("red") objects should reference different CPoint instance (cuz they have their own position data), but they reference the same instance even though I set the default parameter like 'pos = CPoint()'.

Can someone please explain why they're referencing the same instance, and how to get around it?


Solution

  • This is how python does argument defaults, default arguments are initialized once during function declaration and not every time when the function is called like you might expect if you've used other languages like Javascript https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments

    Because of this, the same CPoint() instance is shared between different constructor calls.

    To avoid this behavior you could try setting it inside the function itself.

    class Shape:
        def __init__(self, color = "yellow", filled = True, pos = None):
            if pos is None:
                pos = CPoint()
            #So each pos must reference the different CPoint() instance 
            self.__color = color
            self.__filled = filled
            self.__pos = pos