非常易於理解‘類'與'對象’ 間 name 屬性 引用關系,暨《Python 中的引用和類屬性的初步理解》讀後感
關鍵字:名稱,名稱空間,引用,指針,指針類型的指針(即指向指針的指針) 我讀完後的理解總結: 1. 我們知道,python中的變量的賦值操作,變量其實就是一個名稱name,賦值就是將name引用到一個object對象。name就可以看作是指向object的指針。 2. 有了name看作指針的概念。當一個類A定義時,定義了一個類屬性名字叫class_attr01。 代碼如下: class A(object): class_attr01 = 666 def test(self): pass a_obj = A() print(‘查看a_obj名稱空間中的名字‘,dir(a_obj)) print(‘通過a_obj訪問A類的class_attr01‘, a_obj.class_attr01, id(a_obj.class_attr01)) print(‘校驗A.class_attr01和a_obj.class_attr01是否相同‘, A.class_attr01 is a_obj.class_attr01) # 第一種情況改變a_obj.class_atrr01: a_obj.class_attr01 = 777 print(‘改變a_obj.class_attr01後:‘, a_obj.class_attr01, A.class_attr01) # 第二種情況改變A.class_attr01: 執行第二種情況代碼時,要註釋掉第一種情況代碼,保持兩種情況執行的前提條件是一樣的。A.class_attr01 = 777 print(‘改變A.class_atrr01後:‘, a_obj.class_attr01, A.class_attr01)
------------------------------------------------------------
第一種情況打印結果--------------------------
查看a_obj名稱空間中的名字 [‘test‘, ‘class_attr01‘, ....]
通過a_obj訪問A類的class_attr01 666 2445972197136
校驗A.class_attr01和a_obj.class_attr01是否相同 True
改變a_obj.class_attr01後: 777 666
-----------------------------------------------------------
第二種情況打印結果------------------------
查看a_obj名稱空間中的名字 [‘test‘, ‘class_attr01‘,....]
通過a_obj訪問A類的class_attr01 666 2732605729392
校驗A.class_attr01和a_obj.class_attr01是否相同 True
改變A.class_atrr01後: 777 777
------------------------------------------------------
第一行打印結果分析: 這裏A類沒有定義__init__初始化對象方法。當實例化一個A對象a_obj後,a_obj中應該是沒有任何名稱或者說屬性的。但是從第一行打印結果中我們可以看到 a_obj名稱空間中是有一個class_attr01名稱的。得出第一個結論就是:(在沒有通過__init__初始化方法創建名稱或者對象.方式創建的前提下)類實例化對象時,對象名稱空間中也會創建類屬性同名的名稱,其實可以說 類名稱空間中的名稱都會在對象名稱空間創建相同的名稱。至於名稱指向的什麽,客官往下面看: 第二行和第三行打印結果分析: 再看看,這個名稱引用的對象是什麽呢?從打印結果看就是A中屬性class_attr01引用的對象666。 這時我們就有一個假象: a_obj.class_attr01 和 A.class_attr01是一樣的。通過id(a_obj.class_attr01) 確實 id(A.class_attr01) 是相同的。我們可能得出這樣一個結論: a_obj.class_attr01名稱指向 666 和A.class_attr01名稱指向的是同一對象666。也就是說兩個名稱指向了同一個對象666. 有此結論,那是不是我們可以通過賦值語句改變其中一個指向而不會影響另一個呢? 第一種情況的第四行打印結果分析(註釋掉第二種情況代碼): 通過改變a_obj.class_attr01 = 777 賦值後,我們看到確實改變a_obj.class_attr01沒有改變A.class_attr01指向的666。 下面我們反過來,把代碼的a_obj.class_attr01 = 777 替換為 A.class_atrr01 = 777 第二種情況的第四行打印結果分析(將第二種情況復原,並註釋第二種情況代碼) 違背了我們剛才得到的結論(有此結論,那是不是我們可以通過賦值語句改變其中一個指向而不會影響另一個呢?) 。 因為a_obj.class_attr01 隨同 A.class_attr01 都變為 777了。驚訝:a_obj.class_atrr01 賦值改變不了 A.class_atrr01 反過來則可以。不是說,兩個名稱指向同一對象,賦值一個應該不影響另一個呀。 那只能說明a_obj.class_atrr01 和 A. class_atrr01 還是有區別的。這就是要提到的 “指向指針的指針”。 解釋剛才的現象: a_obj.class_atrr01 在實例化對象時,是指向A.class_atrr01這個名稱的(指針),即a_obj.class_attr01是一個指針,A.class_atrr01也是一個指針; 而a_obj在實例化時a_obj.class_atrr01這個指針是指向A.class_atrr01這個指針的(指向指針的指針);A.class_atrr01這個指針是指向666這個整數對象的。 所以,a_obj.class_atrr01解析引用對象時也是引用到了666。但是在賦值777後a_obj.class_atrr01指向了777對象,而不是指針了,所以解析後就編程777,而A.class_atrr01沒有變,還是指向666. 反過來,實例化a_obj後,只對A.class_atrr01進行重新賦值777,那麽A.class_atrr01就指向777即引用到777對象。這時的a_obj.class_atrr01是一個指向指針的指針,即指針還是指向A.class_atrr01,所以 a_obj.class_atrr01通過指針A.class_atrr01 引用到了相同的對象777. 這樣就能解釋通,上面違背現象的原因了。 有一種特殊情況,就是對於mutable可變對象,可以通過指針的指針改變其值,類也發生變化。 總結: 使用對象和其類相同名稱的屬性時,要考慮當前屬性名稱指向的是哪裏?有沒有被賦值過而改變了其指向。 不知道我表述清楚沒有,不過通過下面博客內容的實驗,也可以驗證我的說法,只不過作者論證沒有提及指針,和指針類型的指針來解釋,不過結論是相似的,即: 類屬性在同類及其子類之間互相影響」必須有一個前提條件:實例建立後,其類屬性從來沒有被重新賦值過,即類屬性依然指向最初所指向的內存地址
Python 中的引用和類屬性的初步理解
最近對Python 的對象引用機制稍微研究了一下,留下筆記,以供查閱。
首先有一點是明確的:「Python 中一切皆對象」。
那麽,這到底意味著什麽呢?
如下代碼:
#!/usr/bin/env python a = [0, 1, 2] # 來個簡單的list # 最初,list 和其中各個元素的id 是這樣的。 print ‘origin‘ print id(a),a for x in a: print id(x), x print ‘----------------------‘ # 我們把第一個元素改改 print ‘after change a[0]‘ a[0] = 4 print id(a),a for x in a: print id(x), x print ‘----------------------‘ # 我們再把第二個元素改改 print ‘after change a[1]‘ a[1] = 5 print id(a),a for x in a: print id(x), x print ‘----------------------‘ # 回頭看看直接寫個0 ,id是多少 print ‘how about const 0?‘ print id(0), 0
運行結果如下:
PastgiftMacbookPro:python pastgift$ ./refTest.py Origin 4299760200 [0, 1, 2] 4298181328 0 4298181304 1 4298181280 2 ---------------------- after change a[0] 4299760200 [4, 1, 2] 4298181232 4 4298181304 1 4298181280 2 ---------------------- after change a[1] 4299760200 [4, 5, 2] 4298181232 4 4298181208 5 4298181280 2 ---------------------- how about const 0? 4298181328 0
從「Origin」部分來看,list 中各個元素的地址之間都正好相差24,依次指向各自的數據——這讓我想到了數組。
當修改a[0] 的值之後,發現,a[0] 的地址發生了變化。也就是說,賦值語句實際上只是讓a[0] 重新指向另一個對象而已。此外,還註意到,a[0] 的地址和a[2]的地址相差48(2個24)。
當再次修改a[1] 之後,同樣地,a[1] 的地址也發生變化,有趣的是,這次a[1] 的地址和a[0] 的地址又相差24,和原先的a[2] 相差72(3個24)。
最後,當直接把數字0的地址打印出來後,發現它的地址和最開始的a[0] 的地址完全一樣。
至此,基本可以說明,就算是list 中的元素,其實也是引用。修改list 中的元素,實際上還是在修改引用而已。
對於Python 中類屬性,有人提到過「類屬性在同一類及其子類之間共享,修改類屬性會影響到同一類及其子類的所有對象」。
這裏提到的:http://www.cnblogs.com/vamei/archive/2012/06/02/2532018.html
聽著挺嚇人,但仔細研究之後,其實倒也不是什麽大不了的事情。
如下代碼:
#!/usr/bin/env python class Bird(object): name = ‘bird‘ talent = [‘fly‘] class Chicken(Bird): pass bird = Bird(); bird2 = Bird(); # 同類實例 chicken = Chicken(); # 子類實例 # 最開始是這樣的 print ‘Original attr‘ print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print ‘----------------------------‘ # 換個名字看看 bird.name = ‘bird name changed!‘ print ‘after changing name‘ print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print ‘----------------------------‘ # 洗個天賦試試(修改類屬性中的元素) bird.talent[0] = ‘walk‘ print ‘after changing talent(a list)‘ print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print ‘----------------------------‘ # 換個新天賦樹(整個類屬性全換掉) bird.talent = [‘swim‘] print ‘after reassign talent‘ print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print ‘----------------------------‘ # 洗掉新天賦樹(對新來的類屬性中的元素進行修改) bird.talent[0] = ‘dance‘ print ‘changing element after reassigning talent‘ print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print ‘----------------------------‘
運行結果:
PastgiftMacbookPro:python pastgift$ ./changeAttributeTest.py Original attr 4301998000 bird 4301857352 [‘fly‘] 4301998000 bird 4301857352 [‘fly‘] 4301998000 bird 4301857352 [‘fly‘] ---------------------------- after changing name 4301986984 bird name changed! 4301857352 [‘fly‘] 4301998000 bird 4301857352 [‘fly‘] 4301998000 bird 4301857352 [‘fly‘] ---------------------------- after changing talent(a list) 4301986984 bird name changed! 4301857352 [‘walk‘] 4301998000 bird 4301857352 [‘walk‘] 4301998000 bird 4301857352 [‘walk‘] ---------------------------- after reassign talent 4301986984 bird name changed! 4301859512 [‘swim‘] 4301998000 bird 4301857352 [‘walk‘] 4301998000 bird 4301857352 [‘walk‘] ---------------------------- changing element after reassigning talent 4301986984 bird name changed! 4301859512 [‘dance‘] 4301998000 bird 4301857352 [‘walk‘] 4301998000 bird 4301857352 [‘walk‘] ----------------------------
在「Origin」的時候,同類對象,子類對象的相同類屬性的地址都是相同的——這就是所謂的「共享」。
修改name 之後,只有被修改的對象name 屬性發生變化。這是因為對name的賦值操作實際上就是換了一個字符串,重新引用。字符串本身並沒有發生變化。所以並沒有在同類和子類之間產生互相影響。
接下來,修改talent 中的元素。這時,情況有所改變:同類及其子類的talent 屬性都一起跟著變了——這很好理解,因為它們都引用的內存地址都一樣,引用的是同一個對象。
再接下來,給talent 重新賦值,也就是改成引用另外一個對象。結果是只有本實例的talent 屬性變化了。從內存地址可以看出,本實例和其他實例的talent 屬性已經不再指向相同的對象了。就是說「至此,本實例已經是圈外人士了」。
那麽,最後再次修改talent 中元素後,對其他實例無影響的結果也是很好理解了。因為已經是「圈外人士」了嘛,我再怎麽折騰也都是自己的事情了。
所以,「類屬性在同類及其子類之間互相影響」必須有一個前提條件:實例建立後,其類屬性從來沒有被重新賦值過,即類屬性依然指向最初所指向的內存地址。
最後提一下對象屬性
如下代碼:
#!/usr/bin/env python class Bird(object): def __init__(self): self.talent = [‘fly‘] bird = Bird() bird2 = Bird() # 剛開始的情形 print ‘Origin‘ print id(bird.talent), bird.talent print id(bird2.talent), bird2.talent print ‘--------------------‘ # 修改其中一個對象的屬性 bird.talent[0] = ‘walk‘ print ‘after changing attribute‘ print id(bird.talent), bird.talent print id(bird2.talent), bird2.talent print ‘--------------------‘ # 作死:兩個對象的屬性指向同一個內存地址,再修改 bird.talent = bird2.talent bird.talent[0] = ‘swim‘ print ‘assign to another attribute and change it‘ print id(bird.talent), bird.talent print id(bird2.talent), bird2.talent print ‘--------------------‘
運行結果:
PastgiftMacbookPro:python pastgift$ ./changeAttributeTest2.py Origin 4299867632 [‘fly‘] 4299760200 [‘fly‘] -------------------- after changing attribute 4299867632 [‘walk‘] 4299760200 [‘fly‘] -------------------- assign to another attribute and change it 4299760200 [‘swim‘] 4299760200 [‘swim‘] --------------------
由於對象屬性就算內容完全一樣(剛初始化後的屬性內容一般都是一樣的),也會分配到完全不同的內存地址上去。所以不存在「同類對象之間影響」的情況。
但如果讓一個對象的屬性和另一個對象的屬性指向同一個地址,兩者之間(但也僅限兩者之間)便又互相牽連起來。
非常易於理解‘類'與'對象’ 間 name 屬性 引用關系,暨《Python 中的引用和類屬性的初步理解》讀後感