实例化Python子类时覆盖了基类属性

3 投票
3 回答
2607 浏览
提问于 2025-04-17 19:27

这段代码看起来是这样的:

class A(object):
    x = 0
    y = 0
    z = []
    def __init__(self):
        super(A, self).__init__()
        if not self.y:
            self.y = self.x
        if not self.z:
            self.z.append(self.x)

class B(A):
    x = 1

class C(A):
    x = 2

print C().y, C().z
print B().y, B().z

输出结果是:

2 [2]
1 [2]

为什么 z 被覆盖了,但 y 没有呢?这是因为它不是不可变类型吗?我查了Python的文档,但没有找到解释。

3 个回答

0

问题在于,xy 是不可变的,而 z 是可变的,并且你对它进行了修改。

self.z.append() 并不是替换 z,而是往 z 里添加一个新项目。

当你运行 C() 并在 print C().y, C().z 中打印时(这会创建两个不同的 C 对象),self.z 不再是 False,因为它已经不再是空的了。

如果你把这两行 print 的顺序调换,你会发现输出是

1 [1]
2 [1]
0

当Python执行的内容时,它会创建一个单独的列表对象,并把它赋值给变量z。因为子类没有重新定义这个变量,而且在Python中,列表是通过引用来存储的,所以这三个类都共享同一个z列表。也就是说,第一个实例化的类会填充z,而后面的类只会得到已经填好的内容。虽然你改变了列表的内容,但并没有改变z指向的是哪个列表。

这对y没有影响,因为你是直接把整数赋值到对象的内部字典中,替换了之前的值。

要解决这个问题,可以在构造函数里创建你的数组,这样就可以赋值了:

class A(object):
    x = 0
    y = 0
    z = None
    def __init__(self):
        super(A, self).__init__()
        if not self.y:
            self.y = self.x
        if not self.z:
            # create a new list
            z = [self.x]

class B(A):
    x = 1

class C(A):
    x = 2

print C().y, C().z
print B().y, B().z
3

是的,这个问题的原因在于一个是不可变的,另一个是可变的。更准确地说,是因为你在改变一个而不是另一个。(重要的不是对象“是否可变”,而是你是否真的去改变它。)另外,你还在使用类变量而不是实例变量(可以参考这个问题)。

在你的类定义中,你创建了三个类变量,这些变量在所有类的实例之间是共享的。在你创建了类的一个实例后,如果你在这个实例上使用self.x,它不会在实例中找到x这个属性,而是会去类中查找。同样,self.z也会在类中查找并找到那个类变量。需要注意的是,因为你把z设为类变量,所以在所有实例之间(包括所有子类的实例,除非它们重写了z),只有一个共享的列表。

当你执行self.y = self.x时,你是在这个实例上创建了一个新的属性,也就是一个实例属性。

但是,当你执行self.z.append(...)时,你并没有创建一个新的实例变量。实际上,self.z是在查找存储在类中的列表,然后append方法是在改变这个列表的内容。这里没有“覆盖”的概念。只有一个列表,当你进行添加操作时,你是在修改它的内容。(只添加一个项目,因为你有if not self.z,所以在你添加一个之后,这个条件变为假,后续的调用就不会再添加任何东西。)

总结一下,读取一个属性的值和给它赋值是两回事。当你读取self.x的值时,你可能是在获取一个存储在类中的值,这个值是所有实例共享的。然而,如果你给self.x赋值,你总是给一个实例属性赋值;如果类中已经有一个同名的类属性,你的实例属性会隐藏掉那个类属性。

撰写回答