最近對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
從每個資料中依次對應的元素之間依次設定為各自的資料來源(Olist 中依次相依次依次為各自的地址。 —這讓我想到了陣列。
當修改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 中類別屬性,有人提到「類別屬性在同一類別及其子類別之間共享,修改類別屬性會影響到同一類別及其子類別的所有物件」。
聽著挺嚇人,但仔細研究之後,其實倒也不是什麼大不了的事。
如下程式碼:
#!/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'] --------------------
由於物件屬性就算內容完全一樣(剛初始化後的屬性內容一般都是一樣的),也會被分配到完全不同的記憶體位址上。所以不存在「同類對象之間影響」的情況。
但如果讓一個物件的屬性和另一個物件的屬性指向同一個位址,兩者之間(但也僅限兩者之間)便又互相牽連起來。