鱼C论坛

 找回密码
 立即注册
查看: 1981|回复: 0

[学习笔记] 奇葩的实例属性 - -关于Python中类属性与实例属性的探讨

[复制链接]
发表于 2018-12-16 10:10:36 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 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中类属性不一定是为其实例所共享的。



                               
登录/注册后可看大图

                               
登录/注册后可看大图

                               
登录/注册后可看大图



③、Python中属性查找机制:

Python中属性的获取存在一个向上查找机制,还是拿上面的例子做说明:

Python中一切皆对象,A属于类对象obj1属于实例对象,从对象的角度来看,Aobj1是两个无关的对象,但是,Python通过下面的查找树建立了类对象A与实例对象obj1obj2之间的关系。

                    A
                    |
             -------------
            |              |
            |              |
           obj1         obj2

原谅我不会画图)   


当调用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中的属性设置是针对对象本身进行的;



                               
登录/注册后可看大图

                               
登录/注册后可看大图

                               
登录/注册后可看大图

                               
登录/注册后可看大图

                               
登录/注册后可看大图

                               
登录/注册后可看大图


                               
登录/注册后可看大图

                               
登录/注册后可看大图

                               
登录/注册后可看大图

                               
登录/注册后可看大图

                               
登录/注册后可看大图

                               
登录/注册后可看大图


①、从头再看对情形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:不要看我的回答,我的回答是不正确的,大家看塔利班大神的回答就可以了。










本帖被以下淘专辑推荐:

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-11-22 07:40

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表