Python中的静态类变量和`self`

2 投票
3 回答
1663 浏览
提问于 2025-04-18 00:13

为什么下面的例子表现得不一样呢?

例子1:foo 看起来像是一个特定于不同对象的类变量。

class A: 
    foo = 1
a, b = A(), A()
a.foo = 5
print b.foo
----------------
Output: 1

例子2:foo 看起来像是一个对所有对象都一样的静态类变量。也许这种行为和列表作为指针的工作方式有关。

class A: 
    foo = []
a, b = A(), A()
a.foo.append(5)
print b.foo
----------------
Output: [5]

例子3:不管用。

class A: 
    self.foo = []
a, b = A(), A()
a.foo.append(5)
print b.foo
----------------
Output: Error

3 个回答

1

这个代码不管用:

class A: 
    self.foo = []

这会引发一个错误。

NameError: name 'self' is not defined

因为在Python中,self并不是一个关键字,它只是一个常用的变量名,通常用来代表类的实例,也就是当你调用这个类的方法时,会把这个实例传进去。

这里有个例子:

class A(object): 
    def __init__(self):
        self.foo = []

a, b = A(), A()
a.foo.append(5)
print(b.foo)

然后返回:

[]

当每个实例被初始化时,它们各自会得到一个自己的列表,这个列表可以通过属性foo来访问。当你修改其中一个列表时,另一个列表不会受到影响,因为它们是存储在内存中不同位置的独立列表。

1

这里的区别不是关于可变性或不可变性,而是进行的操作不同。

在例子1中,这个类有一个属性叫做 foo。在创建对象后,你给这个对象又添加了一个同样名字的属性 foo,这就把原来的属性给“遮住”了。所以类的属性就像是一个“默认值”或者“备用值”。

在例子2中,你对一个对象进行了操作(不过,这个操作只适用于可变对象)。所以通过 A.foo 访问的对象(也可以通过 a.foob.foo 访问,因为没有同名的实例属性)被加上了一个 5

例子3不工作是因为你在使用 self 的地方它并不存在。

注意,例子1同样适用于可变对象,比如列表:

class A: 
    foo = []
a, b = A(), A()
a.foo = []
a.foo.append(5)
b.foo.append(10)
print a.foo # [5]
print b.foo # [10]
print A.foo # [10]

在这里,a.foo 被赋值为一个新的空列表。而 b.foo 因为没有实例属性,继续指向类的属性。所以我们有两个独立的空列表,正如我们在使用 .append() 时所看到的那样。

6

前两个例子都是关于类属性的。它们看起来不同的原因是因为你在做的事情不一样:第一个例子是给一个新值,第二个例子是修改已有的值。

注意,前两个例子做的事情是不一样的。在第一个例子中,你做了 a.foo = 5,这是在赋一个新值。而在第二个例子中,如果你做类似的事情,比如 a.foo = [5],你会看到和第一个例子一样的结果。但实际上你是用 a.foo.append(5) 修改了已有的列表,所以结果就不同了。a.foo = 5 只改变了这个 变量(也就是它指向的值);而 a.foo.append(5) 则是改变了 本身。

(注意,第一种情况没有办法做出第二种情况的效果。也就是说,没有类似 a.foo.add(1) 这样的方式来给 5 加 1。这是因为整数是不可变的,而列表是可变的。但重要的不是列表“是”可变的,而是你确实修改了一个列表。换句话说,能对列表做什么并不重要,重要的是你在具体代码中实际做了什么。)

另外,注意到虽然你在类定义中定义的 foo 是一个类属性,但当你执行 a.foo = 5 时,你实际上是在这个 实例 上创建了一个新的属性。它恰好和类属性同名,但这并不会改变类属性的值,b.foo 仍然能看到原来的值。

最后一个例子之所以不工作,是因为和前两个例子一样,class 块中的代码是在类的作用域内。此时还没有实例,所以没有 self

关于这个问题,StackOverflow上还有很多其他的讨论,我建议你去搜索并阅读一些,以便更全面地理解这个概念。

撰写回答