heidern0612 发表于 2018-12-16 10:10:36

奇葩的实例属性 - -关于Python中类属性与实例属性的探讨

本帖最后由 heidern0612 于 2018-12-16 19:56 编辑

写心得的过程都是自我思考和探索的过程,借鉴和摘抄了了很多论坛前辈和互联网大佬的经验,仓促惶恐间难免错误百出,如有错误,恳请指正,不胜感激。

原贴地址:戳我前进


名字有点奇怪,最近论坛中出现了很多询问类的问题,看来大家跟我一样对类属性和实例属性不太了解。

借这个题目,跟大家一起共同讨论下,为了方便,我还是先举个栗子:

class A():
    b = 1

#情形1
obj1 = A()
obj2 = A()
print(obj1.b,obj2.b,A.b)

#情形2
obj1.b += 2
print(obj1.b,obj2.b,A.b)

#情形3
A.b += 3
print(obj1.b,obj2.b,A.b)

情形1的结果为:1   1   1
情形2的结果为:3   1   1
情形3的结果为:3   4   4


首先为什么会有这个问题呢?

因为b属性被称为类属性,既然是类属性,那么根据经验和佳宇老师的讲课来进行判断,类属性应该是为其实例所共享的。

很自然的,既然是共享关系,那么从类的层次改变b的值,自然其实例的b的值也要跟着变化了。

可是情形3的情况却说明,上面的说法是错的,错哪里呢?这就要从Python的类属性讲起。


①、Python中属性的获取:

对于属性,我们通常采用类.属性或实例.属性的形式调用。

例如上例中的A.b属于类.属性形式,obj1.b属于实例.属性的形式。


②、Python中属性的设置:

对于属性的设置我们通常采用类.属性 = 值或实例.属性 = 值的形式

例如obj1.b = 3

上例中obj1.b += 2等价于obj1.b = obj1.b + 2,这句话包含了属性获取及属性设置两个操作。

OK,重点来了,Python中属性的获取和设置的机制与静态语言是不同的,正是背后机制的不同,导致了Python中类属性不一定是为其实例所共享的。

static/image/hrline/3.gifhttps://fishc.com.cn/static/image/hrline/3.gifhttps://fishc.com.cn/static/image/hrline/3.gif


③、Python中属性查找机制:

Python中属性的获取存在一个向上查找机制,还是拿上面的例子做说明:
Python中一切皆对象,A属于类对象,obj1属于实例对象,从对象的角度来看,A与obj1是两个无关的对象,但是,Python通过下面的查找树建立了类对象A与实例对象obj1、obj2之间的关系。
                  A                  |             -------------            |            |            |            |         obj1         obj2
( {:10_247:}{:10_247:}{:10_247:}原谅我不会画图)   

当调用A.b时,直接从A获取其属性b。
但是情形1中调用obj1.b时,Python按照从obj1到A的顺序由下到上查找属性b。
值得注意的这时候obj1是没有属性b的,于是,Python到类A中去查找,成功找到,并显示出来。
所以,从现象上来看,A的属性b确实是共享给其所有实例的,虽然这里只是从查找树的形式模拟了其关系。


④、Python中的属性设置
问题的关键在于情形2中obj1.b += 2,为什么呢?
上面我们指出obj.b += 2包含了属性获取及属性设置两个操作。
即obj1.b += 2等价于obj1.b = obj1.b + 2。
其中等式右侧的obj.b属于属性获取,其规则是按照上面提到的查找规则进行,即,这时候,获取到的是A的属性b,所以等式左侧的值为12。
第二个操作是属性设置,即obj.b = 12。当发生属性设置的时候,obj1这个实例对象没有属性b,因此会为自身动态添加一个属性b。
由于从对象的角度,类对象和实例对象属于两个独立的对象,所以,这个b属性只属于obj1,也就是说,这时候类对象A和实例对象obj1各自有一个属性b。
那么,在情形3中,再次调用obj1.b时,按照属性调用查找规则,这个时候获取到的是实例对象obj1的属性b,而不是类对象A的属性b。



⑤、对问题探讨的总结:

到这里就可以完满解释上面的问题:

1. Python中属性的获取是按照从下到上的顺序来查找属性;

2. Python中的类和实例是两个完全独立的对象;

3. Python中的属性设置是针对对象本身进行的;

https://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.png
https://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.pnghttps://fishc.com.cn/static/image/hrline/line6.png

①、从头再看对情形1的解释:
因为Python中的属性获取是按照从下到上的顺序来查找的,所以在情形1:
obj1 = A()
obj2 = A()
实例对象obj1和obj2不存在属性b。
证明如下:
>>> obj1.__dict__
{}
>>> obj2.__dict__
{}
所以,此时,obj1.b, obj2.b, A.b实质上都是指A.b。因此,输出同样的结果。



②、对情形2的解释:

因为Python中的类和实例是两个完全独立的对象且Python中的属性设置是针对对象本身进行的,所以在情形2:obj1.b += 2
实质上是对实例对象obj1设置了属性b,并赋值为3。证明如下:>>>> obj1.b = 3
>>> obj1.__dict__
{'b': 3}
>>> obj2.__dict__
{}
因此,再次调用obj1.b时,将获取到的是实例对象obj1的属性b,而不是类对象A的属性b。而对于实例对象obj2,由于其并没有属性b,所以调用obj2.b时,获取到的仍是A的属性b。

③、对情形3的解释:
顺利理解了前两个情形,那么第3个情形就很容易了,改变A的属性b只能影响到类对象A和实例对象obj2,不能影响obj1,因为,obj1存在b,在获取时,不会获取到A的属性。






写在最后的总结:
如果实例对象没有调用类属性的话,会使用类对象的属性值。
如果实例对象调用类属性的话,会创建一个类对象属性一样的副本给自己,再次调用实例对象的类属性时,这个类属性其实已经不是类对象的类属性,而是类属性的副本了,它只属于实例对象。
为了加深大家对类属性和实例属性的理解,我给一个论坛最近关于类的解答问题:地址1。
(P.S:不要看我的回答,我的回答是不正确的{:10_284:},大家看塔利班大神的回答就可以了。)









页: [1]
查看完整版本: 奇葩的实例属性 - -关于Python中类属性与实例属性的探讨