Python中的静态类变量和`self`
为什么下面的例子表现得不一样呢?
例子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 个回答
这个代码不管用:
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中,这个类有一个属性叫做 foo
。在创建对象后,你给这个对象又添加了一个同样名字的属性 foo
,这就把原来的属性给“遮住”了。所以类的属性就像是一个“默认值”或者“备用值”。
在例子2中,你对一个对象进行了操作(不过,这个操作只适用于可变对象)。所以通过 A.foo
访问的对象(也可以通过 a.foo
和 b.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()
时所看到的那样。
前两个例子都是关于类属性的。它们看起来不同的原因是因为你在做的事情不一样:第一个例子是给一个新值,第二个例子是修改已有的值。
注意,前两个例子做的事情是不一样的。在第一个例子中,你做了 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上还有很多其他的讨论,我建议你去搜索并阅读一些,以便更全面地理解这个概念。