类变量和实例变量的区别

6
我已经在Stack Exchange上阅读了许多答案,例如Python - why use "self" in a class?。阅读这些答案后,我明白实例变量是唯一的,每个类的实例都有自己的实例变量,而类变量则在所有实例之间共享。
在尝试中,我发现以下代码输出[1]:
class A:
    x = []
    def add(self):
        self.x.append(1)

x = A()
y = A()
x.add()

print "Y's x: ", y.x

然而,这段代码会输出10,但我认为应该输出11:
class A:
    x = 10
    def add(self):
        self.x += 1

x = A()
y = A()
x.add()

print "Y's x: ", y.x

为什么当我运行 x.add() 时,A 类变量没有被更新?由于我在编程方面经验不足,请多多包涵。


谢谢 @mbatchkarov,我不知道我怎么会忽略了那个 :) - MayankJain
1个回答

12
类变量被实例属性“遮蔽”。这意味着在查找属性时,Python 首先查找实例,然后查找类。此外,在对象(例如 self)上设置变量始终会创建实例变量 - 它永远不会更改类变量。
这意味着当您在第二个示例中执行以下操作时:
self.x += 1

在这种情况下(详见脚注),它等同于:
self.x = self.x + 1

Python的作用是:
  1. 查找self.x。此时,self没有实例属性x,因此找到类属性A.x,其值为10
  2. 评估RHS,得出结果11
  3. 将此结果分配给self的新实例属性x
因此,在下面查找x.x时,您会获得在add()中创建的新实例属性。查找y.x时,仍然会获得类属性。要更改类属性,必须明确使用A.x += 1 - 查找仅在读取属性值时发生。
你的第一个例子是一个经典的陷阱,也是你不应该将类属性用作实例属性的“默认”值的原因。当你调用:
self.x.append(1)

这里没有对 self.x 进行赋值。(改变可变对象的内容,比如 list,不同于赋值。)因此,不会添加新的实例属性到 x 上来遮盖它,并且之后查找 x.xy.x 会得到相同的类属性列表。


注意: 在Python中,x += y并不总是等同于x = x + y。Python允许你分别覆盖类型的原地运算符和普通运算符。这在大多数情况下对于可变对象是有意义的,其中就地版本将直接更改内容而无需重新分配表达式的左侧。然而,不可变对象(例如第二个示例中的数字)不会覆盖就地运算符。在这种情况下,该语句会被解释为常规加法和重新赋值,从而解释了您看到的行为。

(我从此SO答案中摘取了以上内容,请参阅那里以获取更多详细信息。)


实际上,“x += ...”并不总是等同于“x = x + ...”。如果您将代码更改为使用列表类变量的“self.x += [1]”而不是“self.x.append(1)”,则可以观察到这一点。 - user395760
жҲ‘жғіиҙЁз–‘ x+=y йҖҡеёёзӯүеҗҢдәҺ x=x+yпјҢеӣ дёә __iadd__ е’Ң __add__ жҳҜдёҚеҗҢзҡ„гҖӮ - DSM
@DSM 嗯。对于这个例子来说似乎可以工作,我会查看文档,看看能否不使用那种模糊的措辞来表达它。编辑:完成。 - millimoose
你是什么意思,你不应该将类属性用作实例属性的“默认”值? - MayankJain
@Mayank 这是一种有点不可靠的模式,您可以使用它们来“初始化”实例属性,而不是将该代码放入 __init__() 中。对于不可变值,它可以工作,但如果您在实例之间共享可变值,则会出现问题。这基本上就是您在第二个示例中所做的,如果您打算将类变量 x 真正用作初始化 self.x 的方式。 - millimoose
非常感谢您提供详细的答案! - MayankJain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接