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
;
首先為什么會有這個問題呢?
因為aaa
屬性被稱為類屬性,既然是類屬性,那么根據從C++/Java
這種靜態語言使用的經驗來判斷,類屬性應該是為其實例所共享的。很自然的,既然是共享關系,那么從類的層次改變aaa
的值,自然其實例的aaa
的值也要跟着變化了。
可是情形3的情況卻說明,上面的說法是錯的。
錯哪里呢?
要從Python的類屬性講起
Python中類屬性的含義
Python屬於動態強類型的語言,在很多地方和靜態語言不同,因此,不能把靜態語言的規則套到動態語言上來。其中,類屬性就是一個很好的例子。
Python中屬性的獲取
對於屬性,我們通常采用類.屬性或實例.屬性的形式調用。
例如上例中的AAA.aaa
屬於類.屬性形式,obj1.aaa
屬於實例.屬性的形式
Python中屬性的設置
對於屬性的設置我們通常采用類.屬性 = 值或實例.屬性 = 值的形式
例如obj1.aaa = 3
上例中obj1.aaa += 2
等價於obj1.aaa = obj1.aaa + 2
,這句話包含了屬性獲取及屬性設置兩個操作
OK,重點來了,Python中屬性的獲取和設置的機制與靜態語言是不同的,正是背后機制的不同,導致了Python中類屬性不一定是為其實例所共享的
Python中屬性查找機制
Python中屬性的獲取存在一個向上查找機制,還是拿上面的例子做說明:
Python中一切皆對象,AAA
屬於類對象,obj1
屬於實例對象,從對象的角度來看,AAA
與obj1
是兩個無關的對象,但是,Python通過下面的查找樹建立了類對象AAA
與實例對象obj1
、obj2
之間的關系。
如圖所示
1
2
3
4
5
AAA
|
-----
| |
obj1 obj2
(圖畫的不好,見諒 -.-!!!)
當調用AAA.aaa
時,直接從AAA
獲取其屬性aaa
。
但是情形1中調用obj1.aaa
時,Python按照從obj1
到AAA
的順序由下到上查找屬性aaa
。
值得注意的這時候obj1
是沒有屬性aaa
的,於是,Python到類AAA
中去查找,成功找到,並顯示出來。所以,從現象上來看,AAA
的屬性aaa
確實是共享給其所有實例的,雖然這里只是從查找樹的形式模擬了其關系。
Python中的屬性設置
原帖子的作者也指出問題的關鍵在於情形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中的屬性設置是針對對象本身進行的;
對情形1的解釋
因為Python中的屬性獲取是按照從下到上的順序來查找的,所以在情形1:
1
2
obj1 = AAA()
obj2 = AAA()
實例對象obj1
和obj2
不存在屬性aaa
。
證明如下:
1
2
3
4
>>> obj1.__dict__
{}
>>> obj2.__dict__
{}
所以,此時,obj1.aaa, obj2.aaa, AAA.aaa
實質上都是指AAA.aaa
。因此,輸出同樣的結果。
對情形2的解釋
因為Python中的類和實例是兩個完全獨立的對象且Python中的屬性設置是針對對象本身進行的,所以在情形2:
1
obj1.aaa += 2
實質上是對實例對象obj1
設置了屬性aaa
,並賦值為12
。證明如下:
1
2
3
4
5
>>> obj1.aaa = 3
>>> obj1.__dict__
{'aaa': 3}
>>> obj2.__dict__
{}
因此,再次調用obj1.aaa
時,將獲取到的是實例對象obj1
的屬性aaa
,而不是類對象AAA
的屬性aaa
。而對於實例對象obj2
,由於其並沒有屬性aaa
,所以調用obj2.aaa
時,獲取到的是AAA
的屬性aaa
。
對情形3的解釋
順利理解了前兩個情形,那么第3個情形就很容易了,改變AAA
的屬性aaa
只能影響到類對象AAA
和實例對象obj2
,不能影響obj1
,因為,obj1
存在aaa
,在獲取時,不會獲取到AAA
的屬性。
寫在最后的話
問題本身很簡單,但是通過對這個問題的探討,可以深入理解Python作為一個動態語言,在OOP的機制上與靜態語言的差別。
最關鍵的地方在於兩點:
1. 理解Python是如何利用查找樹的機制來模仿類及實例之間的關系;
2. 理解動態語言是可以動態設置屬性的