为什么实例变量在这段代码中引用同一个对象?

Why Instance variables reference the same object in this code?

我想在has-a关系中实现Shapeclass中的move方法(所以Shapeclass不继承CPointclass),但是在设置class变量。

给定的代码是:

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()

结果是:

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

结果应该是这样的:

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

并且我在python导师上执行了代码,代码的可视化是这样的: python tutor visualization

所以 Shape() 和 Shape("red") 对象应该引用不同的 CPoint 实例(因为它们有自己的位置数据),但它们引用相同的实例,即使我设置了默认参数 'pos = CPoint()'.

谁能解释一下为什么他们引用同一个实例,以及如何解决这个问题?

这就是 python 参数默认值的方式,默认参数在函数声明期间初始化一次,而不是每次调用函数时都像您使用其他语言如 Javascript https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments

因此,同一个 CPoint() 实例在不同的构造函数调用之间共享。

要避免这种行为,您可以尝试在函数本身内进行设置。

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