摘要:之所以想寫這個文章是因為碰巧看到網上一篇關于中類屬性及實例屬性區別的帖子。中屬性的獲取對于屬性,我們通常采用類屬性或實例屬性的形式調用。最關鍵的地方在于兩點理解是如何利用查找樹的機制來模仿類及實例之間的關系理解動態語言是可以動態設置屬性的
標題名字有點長。
之所以想寫這個文章是因為碰巧看到網上一篇關于Pyhon中類屬性及實例屬性區別的帖子。因為我之前也被這個問題困擾過,今天碰巧看到了這篇帖子,發現帖子的作者只是描述了現象,然后對原因的解釋比較含糊,并沒有從根本上解釋這個問題,所以才想寫一下我對這個問題的想法。
性子急的可以直接跳到最后看總結。
原帖子地址
為了方便對比,我還是使用原帖子的例子:
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中屬性的獲取
對于屬性,我們通常采用類.屬性或實例.屬性的形式調用。
例如上例中的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之間的關系。
如圖所示
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。因此,輸出同樣的結果。
對情形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. 理解動態語言是可以動態設置屬性的
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37519.html
摘要:本篇內容將從鴨子類型的動態協議,逐漸過渡到使接口更明確能驗證實現是否符合規定的抽象基類。抽象基類介紹完動態實現接口后,現在開始討論抽象基類,它屬于靜態顯示地實現接口。標準庫中的抽象基類從開始,標準庫提供了抽象基類。 《流暢的Python》筆記。本篇是面向對象慣用方法的第四篇,主要討論接口。本篇內容將從鴨子類型的動態協議,逐漸過渡到使接口更明確、能驗證實現是否符合規定的抽象基類(Abst...
摘要:中的類都是單例模式一天,一同事問我這樣一個問題。與方法屬于新式類,即屬于類。方法在實例被創建之后被調用,該方法僅僅是對方法創建的實例進行一些初始化操作。需要注意的是,在重寫方法與方法的參數應該保持一致,否則會有發生。 Python 中的類都是單例模式? 一天,一同事問我這樣一個問題。這是一個奇怪的問題,可能你也這么認為。這里先不做解釋,我們先來看看 __new__ 和 __init__...
摘要:本篇繼續學習之路,實現更多的特殊方法以讓自定義類的行為跟真正的對象一樣。之所以要讓向量不可變,是因為我們在計算向量的哈希值時需要用到和的哈希值,如果這兩個值可變,那向量的哈希值就能隨時變化,這將不是一個可散列的對象。 《流暢的Python》筆記。本篇是面向對象慣用方法的第二篇。前一篇講的是內置對象的結構和行為,本篇則是自定義對象。本篇繼續Python學習之路20,實現更多的特殊方法以讓...
摘要:什么是元類剛才說了,元類就是創建類的類。類上面的屬性,相信愿意了解元類細節的盆友,都肯定見過這個東西,而且為之好奇。使用了這個魔法方法就意味著就會用指定的元類來創建類了。深刻理解中的元類 (一) python中的類 今天看到一篇好文,然后結合自己的情況總結一波。這里討論的python類,都基于python2.7x以及繼承于object的新式類進行討論。 首先在python中,所有東西都...
閱讀 2618·2021-09-28 09:36
閱讀 2228·2021-09-07 09:58
閱讀 1492·2019-08-26 13:53
閱讀 1275·2019-08-23 17:53
閱讀 3023·2019-08-23 15:34
閱讀 1850·2019-08-23 15:34
閱讀 2864·2019-08-23 12:04
閱讀 3717·2019-08-23 10:56