为什么更新类属性不会更新该类的所有实例?

时间:2019-10-09 21:14:03

标签: python

假设我有以下课程:

class A:
    arr = []

如果我将arr的实例附加到A,则A的所有实例都会更新。

>>> a1, a2 = A(), A()
>>> a1.arr.append(0)
>>> a1.arr
[0]
>>> a2.arr
[0]
>>> A.arr
[0]

但是,如果我将arr的实例的数组A设置为数组常量,则其他实例将更新:

>>> a1.arr = [1,2,3]
>>> a1.arr
[1, 2, 3]
>>> a2.arr
[0]
>>> A.arr
[0]

为什么会这样?如果class属性是列表,为什么append=之间会有不同的结果?

当class属性不是数组时,我也注意到类似的行为:

class B:
    value = ''
>>> b1, b2 = B(), B()
>>> b1.value = 'hello'
>>> b1.value
'hello'
>>> b2.value
''
>>> B.value
''
>>> B.value = 'goodbye'
>>> b1.value
'hello'
>>> b2.value
'goodbye'
>>> B.value
'goodbye'

当class属性是字符串时,为什么行为看起来有所不同?如果已经设置了b1的值,为什么B.value = ...仅更新b2的值而不更新b1的值?

6 个答案:

答案 0 :(得分:1)

我相信this answer会说明正在发生的事情。

在类A中,arr class属性

  

... Foo [A]的所有实例共享foovar [arr]

.append()时,您是直接在列表对象arr上进行操作。分配(a1.arr = [1, 2, 3]时,您将创建一个新的列表对象,并将其分配为self.arr上的实例属性(有效地为a1),该实例属性优先于类属性{{1 }}。

  

如果我们不触摸foovar,则f和Foo都是相同的。但是,如果我们更改f.foovar ... <<代码段>> ...,我们将添加一个实例属性,该实例属性将有效屏蔽Foo.foovar的值。现在,如果我们直接更改Foo.foovar,它不会影响我们的foo实例:

答案 1 :(得分:1)

定义class variable并为其分配列表时,列表的地址将分配给class variable

class A:
    arr = []

这就是为什么在第一种情况下,将0附加到arr时,它将被添加到所有对象的arr中。

当您分配a1.arr = [1,2,3]时,对象arra1的地址就会改变,这就是a2.arr不变的原因!

关于第二种情况,您正在将字符串变量的值分配给value。因此,如果您更改b1.value,则不会更改b2.value

class B:
    value = ''

顺便说一句,其他语言中的问题恰恰与referencevalue之间的区别有关。

答案 2 :(得分:1)

您对类属性和实例属性的处理感到困惑。实例属性将 default 设置为class属性。但是,当您专门更改实例时,将创建一个实例属性。让我们按照B类逐步学习:

class B:
    value = ''
# You have a single attribute, `B.value`

b1, b2 = B(), B()
b1.value = 'hello'
# This shadows b1's reference to B.value,
# inserting a local reference to its own attribute of the same name.
# You can check this with the id() function

b2.value   # this still refers to the class attribute.

从这里清楚吗?

答案 3 :(得分:0)

print ("IN THE MIDDLE OF THE PYTHON CALL, BEFORE!")
f= open("/etc/motd","w+") 
f.write("!MOTD!\n")
f.close()
print ("IN THE MIDDLE OF THE PYTHON CALL, AFTER WRITE!")

如果查询类实例(class C: class_attribute=2 def __init__(self): self.instance_attribute='boo' )的属性,则返回的值是实例属性(如果存在),如果不存在,则返回值

如果您分配给实例(my_instance.foo),则会创建一个实例属性,该实例属性可以具有相同的名称,并且会遮盖该类属性

答案 4 :(得分:0)

名称和绑定:“ =”的作用

简而言之,Python没有真正的变量。您看到的变量实际上是一个名称(例如其他语言的别名)。总是称为 Assignment 的运算符=,将名称 bind 绑定到对象。 (在Python中,一切都是对象)

例如:

x = 3

=并没有真正改变x的值,因为实际上没有变量x包含值。
而是创建一个不可变的对象 3,并为x命名 bind (类似于C ++的 reference )。 >)

所以,如果我们这样做

>>> a = [1,2]
>>> b = a
>>> print(id(a)) # id(object) will return the address of object in memory
2426261961288
>>> print(id(b))
2426261961288
>>> a is b # operator "is" evaluate whether a and b refer to the same object.  
True
>>> b.append(3)
>>> print(id(b)) # b's address didn't change
2426261961288
>>> print(a)
[1, 2, 3]
>>> print(b)
[1, 2, 3]

首先,a = [1,2]将名称a绑定到列表[1, 2]可变对象。(为了更好的理解,我在此基础上加注对象的昵称​​ OBJ_288
然后,b = ab绑定到a引用的同一对象,即 OBJ_288
您会看到id(a)id(b)相同,这意味着它们的地址是相同的。
b.append(3)实际上更改了绑定对象b(因为b.append指的是 OBJ_288 的方法)。现在 OBJ_288 成为[1, 2, 3]ab绑定的对象。
因此,当我们print(a)print(b)时,结果是相同的。

但是,如果这样做

>>> b = [4, 5, 6]
>>> a is b
False
>>> id(a)
2426261961288
>>> id(b)
2426262048840
>>> print(a)
[1, 2, 3]

当我们为=调用运算符b时,b将绑定到另一个对象(这是我们由[4,5、5, 6],我们给它起个别名 OBJ840
尽管a仍然是 OBJ_288 ,但是print(a)仍然是[1, 2, 3]

有关详细信息,请参阅以下参考资料(如果您具有C ++知识,则更容易理解前两个参考资料):
https://realpython.com/pointers-in-python/#names-in-python
https://eev.ee/blog/2012/05/23/python-faq-passing/
另外,详细规则在《 Python官方参考》中有说明。
https://docs.python.org/3/reference/executionmodel.html#naming-and-binding

范围,类属性和实例属性

在您的代码中:

class A:
    arr = []
>>> a1, a2 = A(), A()
>>> a1.arr.append(0)

a1class A的实例,在Python中,为了解析属性名称,每个实例都创建一个命名空间,这是属性的第一个位置>搜索引用,如果找不到,它将继续在其类名称空间(类属性所在的位置)中搜索。
因此,在您的情况下,由于a1没有名为arr的实例属性,因此它引用类A的属性A.arrappendA.arr的方法,它将修改A.arr,结果是:

>>> A.arr
[0]

但如果您这样做

>>> a1.arr = [1,2,3]

请记住我在名称和绑定中说过:“ =”做什么 = 分配会将其左侧名称绑定到其右侧对象。 br /> 另外,在Python中,名称的分配总是进入最内部的范围(由globalnonlocal指定的除外)。这意味着它将对象 [1,2,3]绑定到a1.arr的实例属性a1,即使以前不存在。 br /> 现在a1具有新的 instance属性 arr,因此a1.arr作为属性名称解析规则,将阴影A.arr。这就是为什么:

>>> a1.arr
[1, 2, 3]

并且类A的类属性A.arr不受影响。

>>> a2.arr
[0]
>>> A.arr
[0]

参考:
https://docs.pythonorg/3/reference/datamodel.html#the-standard-type-hierarchy类实例
https://docs.python.org/3/tutorial/classes.html#a-word-about-names-and-objects

答案 5 :(得分:-1)

如果您问如何避免这种情况?

class A:
    def __init__(self):
        self.ls = []
a,b = A(), A()
a.ls.append(0)

使用

  

__ init __()将使实例独立

这是您正在做的事的一个例子。

class B:
ls = []
def __init__(self):
    pass


c,d = B(),B()
c.ls == d.ls
Out[21]: True

如您所见,它们仍然引用B对象的相同变量。

这是因为

  

一个是类属性,另一个是实例属性。

因此,在实例化之外声明的列表ls是shared by all the instances of B