问题:如何避免实例之间共享类数据?

我想要的是这种行为:

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。如何获得单独的实例以实现所需的行为?

What I want is this behavior:

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]

Of course, what really happens when I print is:

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

Clearly they are sharing the data in class a. How do I get separate instances to achieve the behavior I desire?


回答 0

你要这个:

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

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

You want this:

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

Declaring the variables inside the class declaration makes them “class” members and not instance members. Declaring them inside the __init__ method makes sure that a new instance of the members is created alongside every new instance of the object, which is the behavior you’re looking for.


回答 1

可接受的答案有效,但多一点解释也无妨。

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

在原始代码中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]

但是,问题中令人困惑的情况永远不会发生在诸如数字和字符串之类的不可变对象上,因为如果没有赋值,它们的值就无法更改。例如,类似于原始字符串属性类型的代码可以正常工作:

>>> class a:
    string = ''


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

总结一下:类属性成为实例属性,当且仅当在实例化之后(__init__无论是否在方法中)为它们分配了值时,类属性才成为实例属性。这是一件好事,因为如果在实例化后再也没有为属性分配值,则可以使用静态属性。

The accepted answer works but a little more explanation does not hurt.

Class attributes do not become instance attributes when an instance is created. They become instance attributes when a value is assigned to them.

In the original code no value is assigned to list attribute after instantiation; so it remains a class attribute. Defining list inside __init__ works because __init__ is called after instantiation. Alternatively, this code would also produce the desired output:

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

However, the confusing scenario in the question will never happen to immutable objects such as numbers and strings, because their value cannot be changed without assignment. For example a code similar to the original with string attribute type works without any problem:

>>> class a:
    string = ''


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

So to summarize: class attributes become instance attributes if and only if a value is assigned to them after instantiation, being in the __init__ method or not. This is a good thing because this way you can have static attributes if you never assign a value to an attribute after instantiation.


回答 2

您将“列表”声明为“类级别的属性”,而不是“实例级别的属性”。为了使属性在实例级别范围内,您需要通过在__init__方法(或其他情况,视情况而定)中使用“ self”参数进行引用来对其进行初始化。

您不必严格地在__init__方法中初始化实例属性,但这可以简化理解。

You declared “list” as a “class level property” and not “instance level property”. In order to have properties scoped at the instance level, you need to initialize them through referencing with the “self” parameter in the __init__ method (or elsewhere depending on the situation).

You don’t strictly have to initialize the instance properties in the __init__ method but it makes for easier understanding.


回答 3

尽管可以接受,但我还是要添加一些描述。

做个小运动

首先定义一个类如下:

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也是class的对象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?这就是__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

好了,现在我们使用了selfprint(a.temp)给我们带来了与众不同的价值print(A.temp)

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

Although the accepted anwer is spot on, I would like to add a bit description.

Let’s do a small exercise

first of all define a class as follows:

class A:
    temp = 'Skyharbor'

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

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

So what do we have here?

  • We have a very simple class which has an attribute temp which is a string
  • An __init__ method which sets self.x
  • A change method sets self.temp

Pretty straight forward so far yeah? Now let’s start playing around with this class. Let’s initialize this class first:

a = A('Tesseract')

Now do the following:

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

Well, a.temp worked as expected but how the hell did A.temp work? Well it worked because temp is a class attribute. Everything in python is an object. Here A is also an object of class type. Thus the attribute temp is an attribute held by the A class and if you change the value of temp through A (and not through an instance of a), the changed value is going to be reflected in all the instance of A class. Let’s go ahead and do that:

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

Interesting isn’t it? And note that id(a.temp) and id(A.temp) are still the same.

Any Python object is automatically given a __dict__ attribute, which contains its list of attributes. Let’s investigate what this dictionary contains for our example objects:

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

Note that temp attribute is listed among A class’s attributes while x is listed for the instance.

So how come that we get a defined value of a.temp if it is not even listed for the instance a. Well that’s the magic of __getattribute__() method. In Python the dotted syntax automatically invokes this method so when we write a.temp, Python executes a.__getattribute__('temp'). That method performs the attribute lookup action, i.e. finds the value of the attribute by looking in different places.

The standard implementation of __getattribute__() searches first the internal dictionary (dict) of an object, then the type of the object itself. In this case a.__getattribute__('temp') executes first a.__dict__['temp'] and then a.__class__.__dict__['temp']

Okay now let’s use our change method:

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

Well now that we have used self, print(a.temp) gives us a different value from print(A.temp).

Now if we compare id(a.temp) and id(A.temp), they will be different.


回答 4

是的,如果您希望列表成为对象属性而不是类属性,则必须在“构造函数”中声明。

Yes you must declare in the “constructor” if you want that the list becomes an object property and not a class property.


回答 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

IN SHORT类变量与实例变量无关。

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

So nearly every response here seems to miss a particular point. Class variables never become instance variables as demonstrated by the code below. By utilizing a metaclass to intercept variable assignment at the class level, we can see that when a.myattr is reassigned, the field assignment magic method on the class is not called. This is because the assignment creates a new instance variable. This behavior has absolutely nothing to do with the class variable as demonstrated by the second class which has no class variables and yet still allows field assignment.

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

IN SHORT Class variables have NOTHING to do with instance variables.

More clearly They just happen to be in the scope for lookups on instances. Class variables are in fact instance variables on the class object itself. You can also have metaclass variables if you want as well because metaclasses themselves are objects too. Everything is an object whether it is used to create other objects or not, so do not get bound up in the semantics of other languages usage of the word class. In python, a class is really just an object that is used to determine how to create other objects and what their behaviors will be. Metaclasses are classes that create classes, just to further illustrate this point.


回答 6

为了保护其他实例共享的变量,您每次创建实例时都需要创建新的实例变量。在类中声明变量时,它是类变量,并由所有实例共享。如果要使其实例化,则需要使用init方法重新初始化变量,以引用该实例

Programiz.com的Python对象和类中获取

__init__()功能。每当实例化该类的新对象时,都会调用此特殊函数。

这种类型的功能在面向对象编程(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

To protect your variable shared by other instance you need to create new instance variable each time you create an instance. When you are declaring a variable inside a class it’s class variable and shared by all instance. If you want to make it for instance wise need to use the init method to reinitialize the variable as refer to the instance

From Python Objects and Class by Programiz.com:

__init__() function. This special function gets called whenever a new object of that class is instantiated.

This type of function is also called constructors in Object Oriented Programming (OOP). We normally use it to initialize all the variables.

For example:

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

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。