python中有一些容易忽略的不可變類型(str,integer,tuple,None)
#錯誤演示
In [45]: def demo(lst=[]): ....: lst.append("hello") ....: return lst ....: In [46]: demo() Out[46]: ['hello'] In [47]: demo() Out[47]: ['hello', 'hello']
廖雪峰的python教程有提到這一塊,但並沒有太細致。在這里,由於lst是一個可變參數,而demo在初始化時lst參數指向一個[]的內存空間,之后每一次調用,[]這個內存空間都append一個“hello”,而由於lst依然指向這個內存空間,所以就會看到demo函數調用的奇怪現象,解決問題的辦法就是引入不可變類型。
#正確演示 In [54]: def demo(lst=None): ....: lst=[] ....: lst.append("hello") ....: return lst ....: In [55]: demo() Out[55]: ['hello'] In [56]: demo() Out[56]: ['hello']
在正確演示中,將lst初始化為None,這樣lst就是一個不可變參數,但是不能直接對lst直接使用append,因為只有list才有append方法,因此需要將lst進行真正的初始化:lst=[]
可變類型和不可變類型是一個很容易忽略的知識點,在這里深入進行研究,下面例舉常見的不可變類型和可變類型。
- 不可變(mutable)類型:int, long, float, string, tuple, frozenset
- 可變類型(immutable)類型:list, dict
Python中所有變量都是值的引用,也就說變量通過綁定的方式指向其值。 而這里說的不可變指的是值的不可變。 對於不可變類型的變量,如果要更改變量,則會創建一個新值,把變量綁定到新值上,而舊值如果沒有被引用就等待垃圾回收。下面用int和list分別作為代表進行講解。
#不可變類型 In [31]: id(1),id(2) Out[31]: (4477999936, 4477999968) In [32]: a = 1 In [33]: id(a) Out[33]: 4477999936 In [34]: #當a賦一個新值時,變量a會綁定到新值上 In [35]: a = 3 In [36]: id(a) Out[36]: 4478000000 #可變類型 In [38]: lst = [0] In [39]: id(lst) Out[39]: 4493976328 In [40]: lst = [0,1] In [41]: id(lst) Out[41]: 4499600328
ps:表面上看可變類型,python似乎實現了不同類型的管理方式,其實不是的。其實lst代表地址,它引用的lst[0],lst[1]的內存地址其實是變了的,因為lst[i]就是int(此處),而int就是不可變類型。
另外,我還想延伸一下關於__new__的用法。為什么要放在這里說,待會看了這個例子就會明白。
class Word(str): def __new__(cls, word): word = word.replace(" ","") return str.__new__(cls,word) def __init__(self,word): self.word = word
def __eq__(self, other): return len(self)==len(other)
def main(): a=Word("foorrrdd ") b=Word("sswwss ") print a==b
if __name__ == '__main__': main()
在這段代碼里,可以看到Word類繼承自str,str是一個不可變類型,因此需要使用到__new__這個魔術方法,在這里對word這個形參進行了預處理,然后預處理后的形參word會傳遞給__init__。由於此例此種情形中,a,b指向的是不同的內存空間,即使不用__new__也不會因為實參的傳入導致上面例子出現不斷追加的情況,但顯然這會是一種更為安全的寫法。
(ps:我不是很確定None是不是一個不可變類型,這篇文章只是個人的理解,如果有誤,懇請指正。)