标题名字有点长。
之因此想写这个文章是由于碰巧看到网上一篇关于Pyhon中类属性及实例属性区别的帖子。由于我以前也被这个问题困扰过,今天碰巧看到了这篇帖子,发现帖子的做者只是描述了现象,而后对缘由的解释比较含糊,并无从根本上解释这个问题,因此才想写一下我对这个问题的想法。code
性子急的能够直接跳到最后看总结。
原帖子地址对象
为了方便对比,我仍是使用原帖子的例子:blog
class AAA(): aaa = 10 # 情形1 obj1 = AAA() obj2 = AAA() print obj1.aaa, obj2.aaa, AAA.aaa # 情形2 obj1.aaa += 2 print obj1.aaa, obj2.aaa, AAA.aaa # 情形3 AAA.aaa += 3 print obj1.aaa, obj2.aaa, AAA.aaa
情形1的结果是:10 10 10
;
情形2的结果是:12 10 10
;
情形3的结果是:12 13 13
;get
首先为何会有这个问题呢?
由于aaa
属性被称为类属性,既然是类属性,那么根据从C++/Java
这种静态语言使用的经验来判断,类属性应该是为其实例所共享的。很天然的,既然是共享关系,那么从类的层次改变aaa
的值,天然其实例的aaa
的值也要跟着变化了。
但是情形3的状况却说明,上面的说法是错的。
错哪里呢?
要从Python的类属性讲起it
Python属于动态强类型的语言,在不少地方和静态语言不一样,所以,不能把静态语言的规则套到动态语言上来。其中,类属性就是一个很好的例子。
Python中属性的获取
对于属性,咱们一般采用类.属性或实例.属性的形式调用。
例如上例中的AAA.aaa
属于类.属性形式,obj1.aaa
属于实例.属性的形式
Python中属性的设置
对于属性的设置咱们一般采用类.属性 = 值或实例.属性 = 值的形式
例如obj1.aaa = 3
class
上例中obj1.aaa += 2
等价于obj1.aaa = obj1.aaa + 2
,这句话包含了属性获取及属性设置两个操做经验
OK,重点来了,Python中属性的获取和设置的机制与静态语言是不一样的,正是背后机制的不一样,致使了Python中类属性不必定是为其实例所共享的总结
Python中属性的获取存在一个向上查找机制,仍是拿上面的例子作说明:
Python中一切皆对象,AAA
属于类对象,obj1
属于实例对象,从对象的角度来看,AAA
与obj1
是两个无关的对象,可是,Python经过下面的查找树创建了类对象AAA
与实例对象obj1
、obj2
之间的关系。
如图所示dict
AAA | ----- | | obj1 obj2
(图画的很差,见谅 -.-!!!)
当调用AAA.aaa
时,直接从AAA
获取其属性aaa
。
可是情形1中调用obj1.aaa
时,Python按照从obj1
到AAA
的顺序由下到上查找属性aaa
。
值得注意的这时候obj1
是没有属性aaa
的,因而,Python到类AAA
中去查找,成功找到,并显示出来。因此,从现象上来看,AAA
的属性aaa
确实是共享给其全部实例的,虽然这里只是从查找树的形式模拟了其关系。语言
原帖子的做者也指出问题的关键在于情形2中obj1.aaa += 2
。
为何呢?
上面咱们指出obj.aaa += 2
包含了属性获取及属性设置两个操做。即obj1.aaa += 2
等价于obj1.aaa = obj1.aaa + 2
。
其中等式右侧的obj.aaa
属于属性获取,其规则是按照上面提到的查找规则进行,即,这时候,获取到的是AAA
的属性aaa
,因此等式左侧的值为12
。
第二个操做是属性设置,即obj.aaa = 12
。当发生属性设置的时候,obj1
这个实例对象没有属性aaa
,所以会为自身动态添加一个属性aaa
。
因为从对象的角度,类对象和实例对象属于两个独立的对象,因此,这个aaa
属性只属于obj1
,也就是说,这时候类对象AAA
和实例对象aaa
各自有一个属性aaa
。
那么,在情形3中,再次调用obj1.aaa
时,按照属性调用查找规则,这个时候获取到的是实例对象obj1
的属性aaa
,而不是类对象AAA
的属性aaa
。
到这里就能够完满解释上面的问题:
1. Python中属性的获取是按照从下到上的顺序来查找属性;
2. Python中的类和实例是两个彻底独立的对象;
3. Python中的属性设置是针对对象自己进行的;
由于Python中的属性获取是按照从下到上的顺序来查找的,因此在情形1:
obj1 = AAA() obj2 = AAA()
实例对象obj1
和obj2
不存在属性aaa
。
证实以下:
>>> obj1.__dict__ {} >>> obj2.__dict__ {}
因此,此时,obj1.aaa, obj2.aaa, AAA.aaa
实质上都是指AAA.aaa
。所以,输出一样的结果。
由于Python中的类和实例是两个彻底独立的对象且Python中的属性设置是针对对象自己进行的,因此在情形2:
obj1.aaa += 2
实质上是对实例对象obj1
设置了属性aaa
,并赋值为12
。证实以下:
>>> obj1.aaa = 3 >>> obj1.__dict__ {'aaa': 3} >>> obj2.__dict__ {}
所以,再次调用obj1.aaa
时,将获取到的是实例对象obj1
的属性aaa
,而不是类对象AAA
的属性aaa
。而对于实例对象obj2
,因为其并无属性aaa
,因此调用obj2.aaa
时,获取到的是AAA
的属性aaa
。
顺利理解了前两个情形,那么第3个情形就很容易了,改变AAA
的属性aaa
只能影响到类对象AAA
和实例对象obj2
,不能影响obj1
,由于,obj1
存在aaa
,在获取时,不会获取到AAA
的属性。
问题自己很简单,可是经过对这个问题的探讨,能够深刻理解Python做为一个动态语言,在OOP的机制上与静态语言的差异。 最关键的地方在于两点: 1. 理解Python是如何利用查找树的机制来模仿类及实例之间的关系; 2. 理解动态语言是能够动态设置属性的