如何避免类数据在实例间共享?

184 投票
7 回答
38731 浏览
提问于 2025-04-15 15:41

我想要的是这样的效果:

class a:
    list = []

x = a()
y = a()

x.list.append(1)
y.list.append(2)
x.list.append(3)
y.list.append(4)

print(x.list) # prints [1, 3]
print(y.list) # prints [2, 4]

当然,当我打印的时候,实际发生的是:

print(x.list) # prints [1, 2, 3, 4]
print(y.list) # prints [1, 2, 3, 4]

很明显,它们在类 a 中共享了数据。我该如何获得独立的实例,以实现我想要的效果呢?

7 个回答

21

虽然之前的回答已经很准确了,但我想再补充一点说明。

我们来做个小练习。

首先定义一个类,代码如下:

class A:
    temp = 'Skyharbor'

    def __init__(self, x):
        self.x = x

    def change(self, y):
        self.temp = y

那么我们这里有什么呢?

  • 我们有一个非常简单的类,它有一个属性 temp,这个属性是一个字符串。
  • 一个 __init__ 方法,它用来设置 self.x
  • 一个改变的方法,用来设置 self.temp

到目前为止是不是很简单?现在我们开始玩这个类。首先我们来初始化这个类:

a = A('Tesseract')

现在做以下操作:

>>> print(a.temp)
Skyharbor
>>> print(A.temp)
Skyharbor

好吧,a.temp 按照预期工作了,但 A.temp 是怎么工作的呢?它之所以能工作,是因为 temp 是一个类属性。在 Python 中,一切都是对象。这里的 A 也是 type 类的一个对象。因此,属性 tempA 类的一个属性,如果你通过 A(而不是通过实例 a)来改变 temp 的值,那么这个改变会在所有 A 类的实例中反映出来。我们继续做这个操作:

>>> A.temp = 'Monuments'
>>> print(A.temp)
Monuments
>>> print(a.temp)
Monuments

有趣吧?而且注意 id(a.temp)id(A.temp) 仍然是相同的

任何 Python 对象都会自动获得一个 __dict__ 属性,这个属性包含了它的属性列表。我们来看看这个字典在我们例子中的内容:

>>> print(A.__dict__)
{
    'change': <function change at 0x7f5e26fee6e0>,
    '__module__': '__main__',
    '__init__': <function __init__ at 0x7f5e26fee668>,
    'temp': 'Monuments',
    '__doc__': None
}
>>> print(a.__dict__)
{x: 'Tesseract'}

注意 temp 属性在 A 类的属性中列出,而 x 列在实例中。

那么,如果 a 的实例中没有列出 temp 属性,我们怎么能得到 a.temp 的值呢?这就是 __getattribute__() 方法的魔力。在 Python 中,点语法会自动调用这个方法,所以当我们写 a.temp 时,Python 实际上执行的是 a.__getattribute__('temp')。这个方法会查找属性的值,也就是通过查看不同的地方来找到属性的值。

__getattribute__() 方法的标准实现首先会在对象的内部字典(dict)中查找,然后在对象本身的类型中查找。在这个例子中,a.__getattribute__('temp') 首先执行 a.__dict__['temp'],然后执行 a.__class__.__dict__['temp']

好了,现在我们来使用我们的 change 方法:

>>> a.change('Intervals')
>>> print(a.temp)
Intervals
>>> print(A.temp)
Monuments

现在我们使用了 self,所以 print(a.temp) 的值和 print(A.temp) 的值就不一样了。

现在如果我们比较 id(a.temp)id(A.temp),它们会是不同的。

28

这个被接受的答案是对的,但多解释一下也没坏处。

类属性在创建实例时并不会变成实例属性。只有当给它们赋值时,它们才会变成实例属性。

在原来的代码中,创建实例后并没有给 list 属性赋值,所以它仍然是一个类属性。如果在 __init__ 方法里定义 list,就能正常工作,因为 __init__ 是在实例创建后被调用的。或者,下面这段代码也能得到想要的结果:

>>> class a:
    list = []

>>> y = a()
>>> x = a()
>>> x.list = []
>>> y.list = []
>>> x.list.append(1)
>>> y.list.append(2)
>>> x.list.append(3)
>>> y.list.append(4)
>>> print(x.list)
[1, 3]
>>> print(y.list)
[2, 4]

不过,问题中提到的那种混淆情况在不可变对象(比如数字和字符串)中是不会发生的,因为它们的值不能在没有赋值的情况下改变。比如,类似于原来的代码,如果属性是字符串类型的话,也是可以正常工作的:

>>> class a:
    string = ''


>>> x = a()
>>> y = a()
>>> x.string += 'x'
>>> y.string += 'y'
>>> x.string
'x'
>>> y.string
'y'

所以总结一下:类属性只有在实例创建后被赋值时,才会变成实例属性,不管是在 __init__ 方法里还是其他地方。这样做是好的,因为如果你在实例创建后从不给属性赋值,就可以拥有静态属性。

186

你想要的是这个:

class a:
    def __init__(self):
        self.list = []

在类的声明里面定义变量,会把它们变成“类”的成员,而不是实例的成员。如果你在 __init__ 方法里面定义它们,那么每次创建一个新的对象实例时,都会为这些成员创建一个新的副本,这就是你想要的效果。

撰写回答