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

發表於2016-05-16

標題名字有點長。

之所以想寫這個文章是因為碰巧看到網上一篇關於Pyhon中類屬性及例項屬性區別的帖子。因為我之前也被這個問題困擾過,今天碰巧看到了這篇帖子,發現帖子的作者只是描述了現象,然後對原因的解釋比較含糊,並沒有從根本上解釋這個問題,所以才想寫一下我對這個問題的想法。


性子急的可以直接跳到最後看總結。
原帖子地址

問題描述

為了方便對比,我還是使用原帖子的例子:

情形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屬於例項物件,從物件的角度來看,AAAobj1是兩個無關的物件,但是,Python通過下面的查詢樹建立了類物件AAA與例項物件obj1obj2之間的關係。
如圖所示

(圖畫的不好,見諒 -.-!!!)
當呼叫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:

例項物件obj1obj2不存在屬性aaa
證明如下:

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

對情形2的解釋

因為Python中的類和例項是兩個完全獨立的物件Python中的屬性設定是針對物件本身進行的,所以在情形2:

實質上是對例項物件obj1設定了屬性aaa,並賦值為12。證明如下:

因此,再次呼叫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. 理解動態語言是可以動態設定屬性的

相關文章