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

我想要的是这种行为:

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中的数据。如何获得单独的实例来实现我想要的行为?

34876 次浏览

您将“ list”声明为“ class level property”而不是“ instance level property”。为了在实例级别确定属性的作用域,您需要通过在 __init__方法中使用“ self”参数来初始化它们(或者根据具体情况在其他地方)。

严格来说,您不必在 __init__方法中初始化实例属性,但是这样更容易理解。

你想要这个:

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

在类声明中声明变量使它们成为“类”成员而不是实例成员。在 __init__方法中声明它们可以确保在对象的每个新实例旁边创建一个成员的新实例,这就是您正在寻找的行为。

是的,如果你想让列表变成一个对象属性而不是一个类属性,你必须在“构造函数”中声明。

这个被接受的答案是有效的,但是多一点解释也无伤大雅。

创建实例时,类属性不会成为实例属性。当为它们分配值时,它们将成为实例属性。

在原始代码中,在实例化之后没有为 list属性分配值; 因此它仍然是一个 class 属性。在 __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__方法中。这是一件好事,因为如果在实例化之后从未为属性赋值,那么可以通过这种方式获得静态属性。

因此,这里的几乎每一个反应似乎都忽略了一个特定的点。类变量 永远不会成为实例变量,如下面的代码所示。通过使用元类在类级别拦截变量分配,我们可以看到当 a.myattr 被重新分配时,类上的字段分配魔法方法不会被调用。这是因为作业 创造了一个新的实例变量。这个行为与类变量有 什么都没有的关系,第二个类没有类变量,但是仍然允许字段分配。

class mymeta(type):
def __init__(cls, name, bases, d):
pass


def __setattr__(cls, attr, value):
print("setting " + attr)
super(mymeta, cls).__setattr__(attr, value)


class myclass(object):
__metaclass__ = mymeta
myattr = []


a = myclass()
a.myattr = []           #NOTHING IS PRINTED
myclass.myattr = [5]    #change is printed here
b = myclass()
print(b.myattr)         #pass through lookup on the base class


class expando(object):
pass


a = expando()
a.random = 5            #no class variable required
print(a.random)         #but it still works

类变量与实例变量没有任何关系。

更清楚的是 它们只是碰巧在实例的查找范围内。类变量实际上是类对象本身上的 实例变量。如果需要,也可以使用 元类变量元类变量,因为元类本身也是对象。无论是否用于创建其他对象,一切都是一个对象,因此不要被其他语言使用的单词 class 的语义所束缚。在 python 中,类实际上只是一个用于确定如何创建其他对象及其行为的对象。元类是创建类的类,只是为了进一步说明这一点。

虽然接受的答案是准确的,我想添加一点描述。

让我们做一个小练习

首先定义一个类如下:

class A:
temp = 'Skyharbor'


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


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

这是什么?

  • 我们有一个非常简单的类,它有一个属性 temp,它是一个字符串
  • 设置 self.x__init__方法
  • 更改方法设置 self.temp

到目前为止很简单,是吧? 现在让我们开始处理这个类。让我们先初始化这个类:

a = A('Tesseract')

现在做以下事情:

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

a.temp和预期的一样,但是 A.temp到底是怎么工作的呢?因为 temp 是一个 class 属性,所以它起作用了。Python 中的一切都是一个对象。这里 A 也是类 type的一个对象。因此属性 temp 是由 A类持有的属性,如果您通过 A更改 temp 的值(而不是通过 a的实例) ,更改后的值将反映在 A类的所有实例中。 让我们继续这样做:

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

很有趣,不是吗? 还有 请注意,ABC0和 id(A.temp)仍然是相同的

任何 Python 对象都会自动获得一个 __dict__属性,该属性包含其属性列表。让我们研究一下这个 dictionary 包含了我们的示例对象的哪些内容:

>>> 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甚至没有列出 a.temp,为什么我们会得到一个已定义的值。这就是 __getattribute__()方法的神奇之处。在 Python 中,虚线语法会自动调用这个方法,因此当我们编写 a.temp时,Python 会执行 a.__getattribute__('temp')。该方法执行属性查找操作,即通过查找不同的位置来查找属性的值。

__getattribute__()的标准实现首先搜索对象的内部字典(迪克特) ,然后搜索对象本身的类型。在这种情况下,a.__getattribute__('temp')首先执行 a.__dict__['temp'],然后执行 a.__class__.__dict__['temp']

好,现在让我们使用 change方法:

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

现在我们已经使用了 selfprint(a.temp)给出了一个与 print(A.temp)不同的值。

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

为了保护由其他实例共享的变量,每次创建实例时都需要创建新的实例变量。当你在一个类中声明一个变量时,它的类变量和所有实例共享。如果您希望以实例的方式创建它,那么需要使用 Init方法来重新初始化引用实例的变量

来自 < em > Python Objects and Class by Programiz.com:

每当实例化该类的新对象时,就会调用这个特殊的函数。

这种类型的函数在面向对象中也称为构造函数 编程(OOP)。我们通常使用它来初始化所有的变量。

例如:

class example:
list=[] #This is class variable shared by all instance
def __init__(self):
self.list = [] #This is instance variable referred to specific instance