如何避免类数据在实例间共享?
我想要的是这样的效果:
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 个回答
虽然之前的回答已经很准确了,但我想再补充一点说明。
我们来做个小练习。
首先定义一个类,代码如下:
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
类的一个对象。因此,属性 temp
是 A
类的一个属性,如果你通过 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)
,它们会是不同的。
这个被接受的答案是对的,但多解释一下也没坏处。
类属性在创建实例时并不会变成实例属性。只有当给它们赋值时,它们才会变成实例属性。
在原来的代码中,创建实例后并没有给 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__
方法里还是其他地方。这样做是好的,因为如果你在实例创建后从不给属性赋值,就可以拥有静态属性。
你想要的是这个:
class a:
def __init__(self):
self.list = []
在类的声明里面定义变量,会把它们变成“类”的成员,而不是实例的成员。如果你在 __init__
方法里面定义它们,那么每次创建一个新的对象实例时,都会为这些成员创建一个新的副本,这就是你想要的效果。