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

问题描述 投票:0回答:4

运行下面的示例代码:

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])

为什么

int
S.i
s2
重置为 0,但
list
S.a
没有重置为空?我认为这与不可变
int
与可变
list
有关,但有人可以帮助表达更多细节,在两次 init 调用期间两个类变量发生了什么?

python class-variables
4个回答
2
投票

因此,当您调用

s1.i
s1.a
时,您正在更改实例属性。要更改类属性,请尝试以下操作:

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

在构造函数中初始化

self.a
self.i
。这将创建属于该类的每个实例的实例属性。

构造函数外部声明的

a
i
是类属性,由所有实例共享。

无论使用哪个属性,

s1.a
S.a
都会更新的原因是因为列表是可变的,并且实例和类变量都是对同一列表的引用。


1
投票
self.i += 1

相当于

self.i = self.i + 1

当实例变量不存在时,会在类上查找值,所以在这种情况下,相当于

self.i = S.i + 1

定义

self.i
后,任何进一步的值查找都会在实例变量上进行,而不是在类变量上进行。因此,在这一行之后,您将看到
S.i = 0
s1.i = 1
。由于
S.i
未修改,
s2.i
也变为
1

另一方面,

self.a.append(1)

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


0
投票

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

当您定义类时,并且像在代码开头一样在任何函数外部定义变量时,它会创建 类属性。这些在类的 all 实例之间共享(在您的情况下,

s1
s2
都共享对您的
i
对象和
a
对象的相同引用)。

当你初始化类时,你正在调用

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

但是,列表可变的。因此,当您将

1
添加到列表中时,您并没有创建一个新的实例变量,因此它保留对类属性的相同引用,因此当您第二次初始化类时,它会将其添加到类中创建第一个实例时已填充一次的属性。


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

a = []

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

如果您希望每个新实例都有一个空列表,请将列表定义移至

__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])
    
© www.soinside.com 2019 - 2024. All rights reserved.