如何初始化可变和不可变类变量?



运行下面的示例代码:

class S:
i = 0
a = []
def __init__(self):
self.i += 1
self.a.append(1)
s1 = S()
print((s1.i, s1.a))
s2 = S()
print((s2.i, s2.a))

输出将是:

(1, [1])
(1, [1, 1])

为什么s2intS.i复位为0,而listS.a不复位为空?我认为这与不可变的int和可变的list有关,但是有人可以帮助表达更多的细节,在两个初始化调用期间两个类变量发生了什么?

所以当您调用s1.is1.a时,您正在改变实例属性。要更改类属性,可以这样做:

S.i += 1
S.a.append(1)

在构造函数中初始化self.aself.i。这会创建属于类的每个实例的实例属性。

在构造函数外声明的ai是类属性,由所有实例共享。

无论使用哪个属性,s1.aS.a都会更新,因为列表是可变的,并且实例和类变量都是对同一个列表的引用。

self.i += 1

等价于

self.i = self.i + 1

当实例变量不存在时,将在类中查找该值,因此在此场景中,它相当于

self.i = S.i + 1

定义self.i之后,任何进一步的值查找都是在实例变量上,而不是在类变量上。在这行之后,是S.i = 0s1.i = 1。因为S.i没有被修改,所以s2.i也变成了1

另一方面,
self.a.append(1)

不创建一个新的实例变量,而是向现有的类变量追加一个元素。

class S:
i = 0
a = []
def __init__(self):
self.i += 1
self.a.append(1)
a = []

定义的list是一个类属性。它在定义类时被实例化,并保持相同的列表对象。该类的任何实例都将引用一个列表。

如果您希望每个新实例都有一个空列表,那么将列表定义移动到__init__方法中:

class S:
i = 0
def __init__(self):
self.a = []
self.i += 1
self.a.append(1)

结果:

>>> s1 = S()
>>> print((s1.i, s1.a))
(1, [1])
>>> 
>>> s2 = S()
>>> print((s2.i, s2.a))
(1, [1])

这段特殊代码的编写方式抽象了Python在幕后所做的一些工作,所以让我们来看看它。

当您定义类时,您在任何函数之外定义变量,就像您在代码开头所做的那样,它创建类属性。这些在你的类的所有实例之间共享(在你的情况下,s1s2都共享对你的i对象和a对象的相同引用)。

当您初始化类时,您正在调用__init__函数,该函数在您的代码中首先调用self.i += 1,我认为这是大多数混乱的来源。在Python中,整数是不可变的,因此它们不能被覆盖。通过调用+=,您将删除对旧i变量的引用,并创建一个引用内存中不同位置的新变量。但是因为您现在是在类中的函数中,所以它被定义为实例属性。实例属性在你的类的不同实例之间是而不是共享的。

然而,列表可变的。所以,当你追加1到你的列表,你是不是创建一个新的实例变量,所以它保持相同的引用类属性,因此,当你第二次初始化你的类,它添加到类属性,已经填充了一次,当你创建第一个实例。

最新更新