python类中的类属性阴影

Class attribute shadowing in Python class

本问题已经有最佳答案,请猛点这里访问。

我学习这个和这个是为了理解课堂属性。但对以下代码片段的输出感到困惑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A:
    aliases = None
    name = None

    def __init__(self,name):
        self.name = name
        self.aliases = set([name])

    def add_aliases(self,a):
        self.aliases.add(a)

    def __repr__(self):
        return str(self.name) + str(self.aliases)

arr = []
for i in range(3):
    arr.append(A(i))
    arr[-1].add_aliases(i+1)

for item in arr:
    print item

A.aliases = set([]) ##Modify the static element of class
for item in arr:
    print item

python解释器:2.7.9

输出是

1
2
3
4
5
6
0set([0, 1])
1set([1, 2])
2set([2, 3])
0set([0, 1])
1set([1, 2])
2set([2, 3])

我期待这样的结果。

1
2
3
4
5
6
0set([2, 3])
1set([2, 3])
2set([2, 3])
0set([])
1set([])
2set([])


解释是,当我们编写self.aliases = set([])时,实际上是在创建一个新的实例属性,隐藏class属性。

因此,如果我们使我们的__init__函数如下,我们得到预期的输出。

1
2
3
def __init__(self,name):
    self.name = name
    A.aliases = set([name])  #using the class attribute directly

还要考虑以下代码段:

1
2
3
4
5
6
7
8
9
10
11
12
13
class A:
    aliases = set([])
    name = None

    def __init__(self,name):
        self.name = name
        self.aliases.add(name) # using the class attribute indirectly.

    def add_aliases(self,a):
        self.aliases.add(a)

    def __repr__(self):
        return str(self.name) + str(self.aliases)

因为在本例中,我们不创建实例属性,所以没有阴影。而问题中的测试代码将产生以下输出:

1
2
3
4
5
6
0set([0, 1, 2, 3])
1set([0, 1, 2, 3])
2set([0, 1, 2, 3])
0set([])
1set([])
2set([])

这是预期的,因为类属性在所有实例之间共享。

在这里,A.alias也可以称为self.aliasinit内或任何其他功能。由于它没有隐藏静态属性,所以给出了预期的输出——当所有对象共享一个公共属性时。

不了解这一概念的人在使用不可变数据结构(如string等)时不会注意到任何东西,但在数据结构(如listdictionary的情况下,这可能会令人惊讶。

还应考虑以下对init的定义。

1
2
3
4
def __init__(self,name):
    self.name = name
    self.aliases.add(name) # referring to class attribute
    self.aliases = set([]) # creating a instance attribute

在这个例子中,它最终创建了instance attribute,测试代码产生的输出是:

1
2
3
4
5
6
0set([1])
1set([2])
2set([3])
0set([1])
1set([2])
2set([3])

从这一切我学到:

总是引用类名为的类属性和对象名为的实例属性,即写A.aliases,当您指类名为aliases时,不要写self.aliases来间接引用self.aliases