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

8st*_*ve8 133 python class

我想要的是这种行为:

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]
Run Code Online (Sandbox Code Playgroud)

当然,我打印时真正发生的是:

print(x.list) # prints [1, 2, 3, 4]
print(y.list) # prints [1, 2, 3, 4]
Run Code Online (Sandbox Code Playgroud)

显然,他们正在课堂上分享数据a.如何获得单独的实例来实现我想要的行为?

aby*_*byx 136

你要这个:

class a:
    def __init__(self):
        self.list = []
Run Code Online (Sandbox Code Playgroud)

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

  • 另外一个澄清:如果您要在其中一个实例中重新分配list属性,则不会影响其他实例.因此,如果您执行了类似`x.list = []`的操作,则可以更改它而不会影响其他任何内容.你遇到的问题是`x.list`和`y.list`是相同的列表,所以当你在一个上调用append时,它会影响另一个. (5认同)
  • @AmalTs注意:将类属性用作实例属性的"惰性"默认值被认为是一种不好的做法.即使属性是不可变类型,最好在`__init__`中分配它们. (4认同)
  • 但为什么这种情况只发生在列表上呢?当我在 __init__ 之外声明一个整数或字符串时,它没有在对象之间共享?任何人都可以分享这个概念的任何文档链接吗? (2认同)
  • @AmalTs 看起来你不明白 python 中的赋值是如何工作的。请参阅[此视频](https://www.youtube.com/watch?v=_AEJHKGk9ns) 或[此帖子](http://stackoverflow.com/a/986145/5420829)。您看到的行为是由于您正在改变列表但重新绑定对整数和字符串的引用而引起的。 (2认同)

jur*_*eza 19

接受的答案有效,但更多的解释不会受到伤害.

创建实例时,类属性不会成为实例属性.它们在为其分配值时成为实例属性.

在原始代码中list,实例化后没有为属性赋值; 所以它仍然是一个类属性.在__init__工作中定义列表是因为__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]
Run Code Online (Sandbox Code Playgroud)

但是,问题中的混淆场景永远不会发生在诸如数字和字符串之类的不可变对象上,因为如果没有赋值,它们的值就无法更改.例如,类似于具有字符串属性类型的原始代码的工作没有任何问题:

>>> class a:
    string = ''


>>> x = a()
>>> y = a()
>>> x.string += 'x'
>>> y.string += 'y'
>>> x.string
'x'
>>> y.string
'y'
Run Code Online (Sandbox Code Playgroud)

总结一下:当且仅当在实例化之后为它们分配值时,类属性才变为实例属性,__init__无论是否在方法中.这是一件好事,因为如果您在实例化后从未为属性赋值,则可以使用静态属性.

  • 我知道这是一个旧的答案,但是...不同意“[这是一件好事,因为...]”,因为它不一致,不直观,而且是反模式。它要么是静态属性(类的属性),要么是实例属性。 (2认同)

jld*_*ont 11

您将"list"声明为"类级属性"而不是"实例级属性".为了在实例级别定义属性,您需要通过__init__方法中的"self"参数(或根据具体情况在其他地方)引用来初始化它们.

您不必严格必须初始化方法中的实例属性,__init__但这样可以更容易理解.


Swa*_*nil 8

虽然接受的anwer是现货,但我想补充一点描述.

我们来做一个小练习

首先定义一个类如下:

class A:
    temp = 'Skyharbor'

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

    def change(self, y):
        self.temp = y
Run Code Online (Sandbox Code Playgroud)

那么我们在这里有什么?

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

到目前为止相当直接是啊?现在让我们开始玩这个课程.让我们首先初始化这个类:

a = A('Tesseract')
Run Code Online (Sandbox Code Playgroud)

现在执行以下操作:

>>> print(a.temp)
Skyharbor
>>> print(A.temp)
Skyharbor
Run Code Online (Sandbox Code Playgroud)

好吧,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
Run Code Online (Sandbox Code Playgroud)

有意思不是吗?并注意到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'}
Run Code Online (Sandbox Code Playgroud)

请注意,temp属性列在A类的属性中,同时x为实例列出.

那么为什么我们得到一个定义的值,a.temp如果它甚至没有为实例列出a.那是__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
Run Code Online (Sandbox Code Playgroud)

那么现在我们已经使用了self,print(a.temp)给了我们不同的价值print(A.temp).

现在,如果我们比较id(a.temp)id(A.temp),他们也会有所不同.


小智 5

因此,几乎这里的每一个回应似乎都忽略了一个特定的要点。类变量永远不会成为实例变量,如下面的代码所示。通过利用元类在类级别拦截变量赋值,我们可以看到当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
Run Code Online (Sandbox Code Playgroud)

简而言之,类变量与实例变量无关。

更清楚地说,它们恰好在实例的查找范围内。类变量实际上是类对象本身的实例变量。如果您愿意,您也可以拥有元类变量,因为元类本身也是对象。一切都是对象,无论它是否用于创建其他对象,因此不要受到其他语言使用该词类的语义的束缚。在Python中,类实际上只是一个对象,用于确定如何创建其他对象以及它们的行为。元类是创建类的类,只是为了进一步说明这一点。