古靈精怪的python——地址,淺拷貝與身份運算符
來自專欄深度學堂
首先拋出一個問題,吸引讀者的閱讀興趣(如果您覺得這個不是問題,那麼這篇文章不適合您:)
請看如下代碼:
>>> a = 3>>> b = 3>>> a == bTrue>>> a is bTrue# 這沒問題>>> a = 3>>> b = a>>> a == bTrue>>> a is bTrue# 這看起來也很合理>>> a = (2,3)>>> b = (2,3)>>> a is bFalse # ???why?>>> a == bTrue>>> b = a>>> a is bTrue # ???why?>>> a == bTrue
好了,整篇文章都是圍繞這個問題展開的。長久以來我都習慣用is
而不用==
來進行兩個對象的比較(python中一切皆對象) 直到今天出了一個bug後才了解到這兩者之間的不同,挖到python的一個大坑之餘,不禁出了一身冷汗。。。
用is
還是==
?
補充知識
id() 用於獲取對象在內存中的地址,並以十進位展示出來。如:
>>> a = 3>>> id(a)140602638349720>>> hex(id(a)) # 還原成我們看著更順眼的16進位,但是本文以10進位地址為主(因為懶)0x7fe09a503598
顧名思義,is是「相同」,而==是指兩者之間的」相等「關係。所謂相同,比較的是兩者之間的在內存中的位置,
>>> a = 3>>> id(a)140602638349720>>> b = 3 # b指向的是和a指向的同一塊地址(但是並不意味這改變了a,b也會相應改變)>>> id(b)140602638349720>>> c = a # a的引用複製給c,在內存中其實是指向了用一個對象>>> id(c)140602638349720>>> a is bTrue>>> a is cTrue>>> b is cTrue
我們看到,上面a,b,c的地址相同,所以他們互相之間」相同「
而相等則兩者之間的數值對應相等
>>> a = 3>>> b = a>>> a = 4>>> b3>>> a = [3]>>> b = [3]>>> id(a)4351374184>>> id(b)4351374112>>> a is bFalse >>> a == bTrue>>> a[0] = 4>>> b[3]>>> a = [3]>>> b = a # b就是a的引用,佔得是同一塊地址,而且當a的內容改變時,b也會隨之改變,這和上面# int對象不同,我也不知道為啥要這麼搞。>>> a[0] = 4 >>> b[4]
很多同學看到這肯定是一鍋漿糊了,其實就是一個原則,能用==就不用is。除了一種情況,那就是判斷對象是否是None。
>>> if a is None:... pass
淺拷貝和深拷貝
>>> a = [3]>>> b = a[:] #通過切片賦值,返回的是a的淺拷貝>>> id(a)4351273944>>> id(b)4351374184>>> id(a[0])140602638349720>>> id(b[0]) #list的地址不同140602638349720>>> a is b False>>> a[0] is b[0] #淺拷貝,只拷貝了a的殼[],裡邊的內容仍然是同一個東西,同樣的idTrue>>> a == bTrue>>> a[0] == b[0]True>>> a[0] = 4 # 但是,b的內容不會隨著a的變化而變化>>> b[3]
淺拷貝拷貝了最外層容器,副本中的元素是原容器中元素的引用
我們再看一個例子
>>> Anndy = [Anndy, [age, 24]]>>> Tom = Anndy[:]>>> Cindy = list(Anndy)>>> id(Anndy)4351374040>>> id(Tom)4351373968>>> id(Cindy)4351374616>>> print(Anndy, Tom, Cindy)([Anndy, [age, 24]],[Anndy, [age, 24]],[Anndy, [age, 24]])# 看起來是創建了三個不同的對象,因為他們的id各不相同>>> Tom[0] = Tom>>> Cindy[0] = Cindy>>> print (Anndy, Tom, Cindy)([Anndy, [age, 24]], [Tom, [age, 24]], [Cindy, [age, 24]])# 如果想修改某一個人的名字也沒有什麼問題# 現在我們想把Tom的年齡修改為12歲>>> Tom[1][1] = 12>>> print (Anndy, Tom, Cindy)([Anndy, [age, 12]], [Tom, [age, 12]], [Cindy, [age, 12]])# 震驚!所有人的年齡都變成了12!!!>>> print ([id(x) for x in Anndy])[4351366368, 4351374112] # 看第二個列表的地址>>> print ([id(x) for x in Tom])[4351323592, 4351374112] # 看第二個!>>> print ([id(x) for x in Cindy])[4351366224, 4351374112] # 第一個姓名元素的地址不同,但是第二個列表是同一個
構造方法或切片 [:] 做的是淺拷貝。如果所有元素都是不可變的(比如名字字元串,修改的時候會重新創建對象,僅僅包括原子對象的元組也屬於這種情況),那麼這樣沒有問題,還能節省內存。但是,如果有可變的元素,可能就會導致意想不到的問題,正如剛剛,修改一個人的年齡,所有人的年齡都發生了變化。
所以,如果你想要深拷貝,應該這麼寫
>>> import copy>>> Anndy = [Anndy, [age, 24]]>>> Tom = copy.deepcopy(Anndy)>>> Tom[1][1] = 12>>> print(Tom, Anndy)([Anndy, [age, 12]], [Anndy, [age, 24]]) #這樣寫就沒問題了
另外
不知道剛才你有沒有注意到
>>> a = 3>>> b = 3>>> id(a)140602638349720>>> id(b)140602638349720 # 相同!>>> a is bTrue
Python會對比較小的整數對象進行緩存緩存起來。當整數比較大的時候就會重新開闢一塊內存。
>>> a = 999>>> b = 999>>> id(a)140602638469952>>> id(b)140602638469904 # 不同!>>> a is bFalse
這僅僅是在命令行中執行,而在保存為文件執行,結果是不一樣的,這是因為解釋器做了一部分優化。
#!/usr/bin/env pythona = 3b = 3print(a is b)a = 99999b = 99999print(a is b)結果:TrueTrue[Finished in 0.0s]
這也是為什麼我屢屢用is而不用==,程序運行良好的原因。
總結
- python中,盡量不要用
is
, 除非判斷對象是否為None
。
2. a is b
(相同)一定意味著a == b
(相等),而a == b
(相等) 不一定 a is b
(相同)這點比較好理解
3. 如果函數中傳參等,需要引用、拷貝的,注意是否生成了一個新的對象,即使生成了,內部元素是否是同一個對象的引用?尤其注意切片的使用。必要的時候用copy模塊進行深拷貝而不要用切片這種淺拷貝形式。
推薦閱讀:
※python Web 運維 爬蟲.....一條龍學習視頻教程
※對一些盲目想從事大數據的朋友的警示。
※Python數據採集(爬蟲)淺談
※Python黑帽編程2.5 函數
※用Python入門不明覺厲的馬爾可夫鏈蒙特卡羅(附案例代碼)