当我实例化python子类时,它会覆盖基类的属性

When I instantiate a python subclass it overwrites base class' attribute

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

输出是

1
2
2 [2]
1 [2]

为什么z被覆盖而不是y?是因为它不是不变的类型吗?我查看了python的文档,没有找到解释。


是的,这是因为一个是不可变的,而另一个是不可变的,或者更确切地说,你正在改变一个而不是另一个。(重要的不是对象是否"可变",重要的是你是否真的改变了它。)这也是因为你使用的是类变量而不是实例变量(见这个问题)。

在类定义中,创建三个类变量,在类的所有实例之间共享。创建类的实例后,如果在该实例上执行self.x,它将不会在实例上找到x作为属性,而是在类上查找它。同样的,以东十一〔二〕也要仰望全班,在全班找到一个。请注意,由于您将z设置为类变量,因此只有一个列表在类的所有实例(包括所有子类的所有实例,除非它们覆盖z)之间共享。

当执行self.y = self.x操作时,只在实例上创建一个新属性,即实例属性。

但是,在执行self.z.append(...)操作时,不会创建新的实例变量。相反,self.z查找存储在类中的列表,然后append对该列表进行变异。没有"覆盖"。只有一个列表,当您执行追加时,您将更改其内容。(只追加了一个项目,因为您有if not self.z,所以在追加一个项目之后,这是错误的,后续调用不再追加任何内容。)

结果是,读取属性的值与分配给它的值不同。当您读取EDOCX1的值〔0〕时,您可能正在检索一个存储在类中并在所有实例之间共享的值。但是,如果将值赋给self.x,则总是赋给实例属性;如果已经存在同名的类属性,则实例属性将隐藏该属性。


当python评估class A的主体时,它实例化单个list对象并将其分配给z。由于子类不重写它,而且由于list对象在python中是通过引用存储的,所以这三个类都共享同一个z列表,所以您实例化的第一个类将填充z,然后其余的类将获取放在那里的内容。虽然您更改了列表的内容,但没有更改z所指的列表。

这不会影响y,因为您要将整数直接分配到对象的内部字典中,替换以前的值。

要解决此问题,请在构造函数内创建数组,从而分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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


问题是,xy是不可变的,而z是可变的,你突变了它。

self.z.append()不代替z,只是在z上增加了一个项目。

print C().y, C().z中运行C()之后(创建两个不同的C对象),self.z不再对False进行评估,因为它不再为空。

如果你把两条print线倒过来,你会发现输出是

1
2
1 [1]
2 [1]