实例化Python子类时覆盖了基类属性
这段代码看起来是这样的:
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 个回答
问题在于,x
和 y
是不可变的,而 z
是可变的,并且你对它进行了修改。
self.z.append()
并不是替换 z
,而是往 z
里添加一个新项目。
当你运行 C()
并在 print C().y, C().z
中打印时(这会创建两个不同的 C
对象),self.z
不再是 False
,因为它已经不再是空的了。
如果你把这两行 print
的顺序调换,你会发现输出是
1 [1]
2 [1]
当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
是的,这个问题的原因在于一个是不可变的,另一个是可变的。更准确地说,是因为你在改变一个而不是另一个。(重要的不是对象“是否可变”,而是你是否真的去改变它。)另外,你还在使用类变量而不是实例变量(可以参考这个问题)。
在你的类定义中,你创建了三个类变量,这些变量在所有类的实例之间是共享的。在你创建了类的一个实例后,如果你在这个实例上使用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
赋值,你总是给一个实例属性赋值;如果类中已经有一个同名的类属性,你的实例属性会隐藏掉那个类属性。