關於Python類屬性與實例屬性的討論


標題名字有點長。
之因此想寫這個文章是由於碰巧看到網上一篇關於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 13get

首先爲何會有這個問題呢?
由於aaa屬性被稱爲類屬性,既然是類屬性,那麼根據從C++/Java這種靜態語言使用的經驗來判斷,類屬性應該是爲其實例所共享的。很天然的,既然是共享關係,那麼從類的層次改變aaa的值,天然其實例的aaa的值也要跟着變化了。
但是情形3的狀況卻說明,上面的說法是錯的。
錯哪裏呢?
要從Python的類屬性講起it

Python中類屬性的含義

Python屬於動態強類型的語言,在不少地方和靜態語言不一樣,所以,不能把靜態語言的規則套到動態語言上來。其中,類屬性就是一個很好的例子。
Python中屬性的獲取
對於屬性,咱們一般採用類.屬性實例.屬性的形式調用。
例如上例中的AAA.aaa屬於類.屬性形式,obj1.aaa屬於實例.屬性的形式
Python中屬性的設置
對於屬性的設置咱們一般採用類.屬性 = 值實例.屬性 = 值的形式
例如obj1.aaa = 3class

上例中obj1.aaa += 2等價於obj1.aaa = obj1.aaa + 2,這句話包含了屬性獲取屬性設置兩個操做經驗

OK,重點來了,Python中屬性的獲取和設置的機制與靜態語言是不一樣的,正是背後機制的不一樣,致使了Python中類屬性不必定是爲其實例所共享的總結

Python中屬性查找機制

Python中屬性的獲取存在一個向上查找機制,仍是拿上面的例子作說明:
Python中一切皆對象,AAA屬於類對象obj1屬於實例對象,從對象的角度來看,AAAobj1是兩個無關的對象,可是,Python經過下面的查找樹創建了類對象AAA與實例對象obj1obj2之間的關係。
如圖所示dict

AAA
         |
       -----
      |     |  
    obj1   obj2

(圖畫的很差,見諒 -.-!!!)
當調用AAA.aaa時,直接從AAA獲取其屬性aaa
可是情形1中調用obj1.aaa時,Python按照從obj1AAA的順序由下到上查找屬性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:

obj1 = AAA()  
obj2 = AAA()

實例對象obj1obj2不存在屬性aaa
證實以下:

>>> obj1.__dict__
{}
>>> obj2.__dict__
{}

因此,此時,obj1.aaa, obj2.aaa, AAA.aaa實質上都是指AAA.aaa。所以,輸出一樣的結果。

對情形2的解釋

由於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的解釋

順利理解了前兩個情形,那麼第3個情形就很容易了,改變AAA的屬性aaa只能影響到類對象AAA和實例對象obj2,不能影響obj1,由於,obj1存在aaa,在獲取時,不會獲取到AAA的屬性。

寫在最後的話

問題自己很簡單,可是經過對這個問題的探討,能夠深刻理解Python做爲一個動態語言,在OOP的機制上與靜態語言的差異。 最關鍵的地方在於兩點: 1. 理解Python是如何利用查找樹的機制來模仿類及實例之間的關係; 2. 理解動態語言是能夠動態設置屬性的

相關文章
相關標籤/搜索