假设我有以下课程:
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
的值?
答案 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]
时,对象arr
中a1
的地址就会改变,这就是a2.arr
不变的原因!
关于第二种情况,您正在将字符串变量的值分配给value
。因此,如果您更改b1.value
,则不会更改b2.value
class B:
value = ''
顺便说一句,其他语言中的问题恰恰与reference
和value
之间的区别有关。
答案 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 = a
将b
绑定到a
引用的同一对象,即 OBJ_288 。
您会看到id(a)
与id(b)
相同,这意味着它们的地址是相同的。
b.append(3)
实际上更改了绑定对象b
(因为b.append
指的是 OBJ_288 的方法)。现在 OBJ_288 成为[1, 2, 3]
和a
和b
绑定的对象。
因此,当我们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)
a1
是class A
的实例,在Python中,为了解析属性名称,每个实例都创建一个命名空间,这是属性的第一个位置>搜索引用,如果找不到,它将继续在其类名称空间(类属性所在的位置)中搜索。
因此,在您的情况下,由于a1
没有名为arr
的实例属性,因此它引用类A的属性A.arr
。 append
是A.arr
的方法,它将修改A.arr
,结果是:
>>> A.arr
[0]
但如果您这样做
>>> a1.arr = [1,2,3]
请记住我在名称和绑定中说过:“ =”做什么, = 分配会将其左侧名称绑定到其右侧对象。 br />
另外,在Python中,名称的分配总是进入最内部的范围(由global
或nonlocal
指定的除外)。这意味着它将对象 [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
。