Python資料型態,可變與不可變物件

在Python中,並沒有所謂的primitive type(原始資料型態),所有資料型態都是物件
包括數值型態、字元、字串以及容器(list、dict等)
而所有的變數都是指向某物件的參考
值得一提的是Python中的函式實際上是call by value of reference,而不是call by reference
意思是說呼叫一個函式並傳遞參考進去時,函式裡的變數實際上是那份參考的副本而不是參考本身
所以你可以透過該副本改變參考所指物件的內容,但是在函式內將參考重新綁定到新的物件是對原本的參考沒有影響的
這點與Java的機制是一樣的

物件又分成可變(mutable)與不可變(immutable),顧名思義不可變物件一旦創建後其內容就不能再改變
以容器為例,list、dict與set是可變物件,tuple與frozenset是不可變物件
若嘗試對不可變物件做+=或-=等運算,實際上是將參考綁定到一新的物件
與我們一般的認知:+=是改變原本物件是不一樣的,我們可以使用is運算子來測試
is與==運算子的差別在於前者是比較兩個參考是否綁定到同一物件,後者則是比較兩物件的內容是否相等
所以==回傳True並不代表兩者就是同一個物件
如以下範例:
a=1
b=a #將b與a綁定到同一個整數物件
a is b
True
a+=10
a is b
False
不可變物件為容器時,雖然其所包含的參考不能改變 但參考所指向的物件卻可以改變,聽起來好像很奇怪? 看看以下範例:
a=([1],[2],[3])#創建一個tuple,包含三個指向不同list的參考
a[2].append(5)#將第三個參考所指向的list增加一個元素
以上程式碼不會有任何問題!因為tuple裡面的參考並沒有被指向不同的物件
另外,在Fluent Python中提到了一個有趣的問題,請先看以下程式碼
a=([1],[2])
a[0]+=[3,4,5]
這段程式碼是否能正常運作呢?
答案是:能!但也不能
解譯器會告訴你tuple物件不支援assign操作
但a[0]這個list會被成功地改變!
關鍵在於第二行的執行次序是先對a[0]這個List執行+=操作
然後在將執行結果assign給a[0],到這個動作的時候解譯器才會報錯!
有興趣的人可以用dis.dis看byte codes的運作

最後,在Python中還有一項機制叫做interning(比較近代的語言幾乎都有)
Python會將某些不可變物件只保存一份副本在記憶體中
而每次使用者將不同的參考賦予相同的值時實際上都是綁定到同一個物件
如以下範例
s1="abc"
s2="abc"
print(s1 is s2)
輸出:
True
這麼做除了節省記憶體外也可以節省比較兩個字串的時間
想像一下若你有一個很長的字串需要比較,你需要確認每個字母是否都是一樣,相較之下比較記憶體位置就省時多了
除了字串外,整數有時也有interning,但這之中的細節取決於解譯器的底層實作,程式碼不應該與intering的行為有任何相依
否則移植到不同平台時很可能就無法正常運作了

參考資料:
http://stackoverflow.com/questions/6502575/python-call-by-reference-on-primitive-types
http://stackoverflow.com/questions/15541404/python-string-interning
https://en.wikipedia.org/wiki/String_interning

留言

這個網誌中的熱門文章

MIT演算法開放式課程 Lecture 1: Algorithmic Thinking, Peak Finding

Python中的iterator、iterable、generator