之前的文章我們簡單介紹了一下 Python 中異常處理,本篇文章我們來看一下 Python 中 is 和 == 的區別及深拷貝和淺拷貝。
我們先來看一下在 Python 中的雙等號 == 。
== 是比較兩個對象的內容是否相等,即兩個對象的“值”是否相等,不管兩者在內存中的引用地址是否一樣。
is 比較的是兩個實例對象是不是完全相同,它們是不是同一個對象,占用的內存地址是否相同。即is比較兩個條件:1.內容相同。2.內存中地址相同
1 a = 10000 2 b = 10000 3 print(a == b) # True 4 print(a is b) # True 5 print(id(a)) # 4461408208 6 print(id(b)) # 4461408208 7 8 a = "hello world" 9 b = "hello world" 10 print(a == b) # True 11 print(a is b) # True 12 print(id(a)) # 4461408208 13 print(id(b)) # 4461408208 14 15 a = [11,22,33] 16 b = [11,22,33] 17 print(a == b) # True 18 print(a is b) # False 19 print(id(a)) # 4409720712 20 print(id(b)) # 4409720776
在上面的代碼中,我們分別定義了 a 和 b 兩個變量,通過輸出結果可以發現當變量為字符串或數字時, is 和 == 的輸出結果是一樣的,當為列表時 is 和 == 結果不一樣,通過打印兩個變量的 id 值可以看出兩個 id 值不一樣,這是由於當我們創建列表 a 和 b 時,是分別開辟了兩塊內存來分別存儲這兩個變量,從表象上來看結果是一樣的,但兩個變量所指向的內存地址不一樣,我們將上面的代碼改為如下:
1 a = [11,22,33] 2 b = a 3 print(a == b) # True 4 print(a is b) # True 5 print(id(a)) # 4535062408 6 print(id(b)) # 4535062408
在上面的代碼中,我們並沒有直接給變量 b 賦值,而是讓 b=a,這樣的話 b 和 a 就指向了同一塊內存,所以 a is b 就為 True 了。
上面的代碼我是在 PyCharm 編輯器中實現的,但是在終端命令行實現的話結果卻是不一樣的,如下:
1 >>> a = 10000 2 >>> b = 10000 3 >>> a == b 4 True 5 >>> a is b 6 False 7 >>> id(a) 8 4360555120 9 >>> id(b) 10 4360555216
當我們將 a 和 b 的值變小時,如下:
1 >>> a = 100 2 >>> b = 100 3 >>> a == b 4 True 5 >>> a is b 6 True 7 >>> id(a) 8 4357367984 9 >>> id(b) 10 4357367984
造成上面的原因是因為python對小整數在內存中直接創建了一份,不會回收,所有創建的小整數變量直接從對象池中引用他即可。但是注意Python僅僅對比較小的整數對象進行緩存(范圍為范圍[-5, 256])緩存起來,而並非是所有整數對象。也就說只有在這個[-5,256]范圍內創建的變量值使用is比較時候才會成立。
在 PyCharm 中,當值超過 256 時 is 和 == 的輸出結果仍是一樣,這是因為解釋器也做了一部分優化,對於數字和字符串這類變量都進行了緩存。
我們再來看一下在終端命令行中當變量為字符串時:
1 >>> a = "hello world" 2 >>> b = "hello world" 3 >>> a == b 4 True 5 >>> a is b 6 False 7 >>> id(a) 8 4359747248 9 >>> id(b) 10 4361247408
1 >>> a = "hello" 2 >>> b = "hello" 3 >>> a == b 4 True 5 >>> a is b 6 True 7 >>> id(a) 8 4361199040 9 >>> id(b) 10 4361199040
通過輸出結果可以看出,在命令行中當變量為簡單字符串時輸出結果一致,否則輸出結果不一致,這是由於 Python 對簡單字符串對象也進行了緩存,這樣做的意義是可以優化代碼的運行速度,減少內存消耗。
在上面的代碼中,我們讓 a=b 其實是一種淺拷貝的過程,他們指向的是同一塊內存,當我們改變其中一個變量時,另一個也會變,如下:
1 a = [11,22,33] 2 b = a 3 a[0] = 123 4 print(a) # [123, 22, 33] 5 print(b) # [123, 22, 33]
在 Python 中還有深拷貝,它是重新開辟一塊區域用來存儲變量的,如下:
1 import copy 2 3 a = [11, 22, 33] 4 b = a 5 c = [11, 22, 33] 6 d = copy.deepcopy(a) 7 print(a == b, a is b) # True True 8 print(a == c, a is c) # True False 9 print(a == d, a is d) # True False 10 11 a[0] = 123 12 print(a) # [123, 22, 33] 13 print(b) # [123, 22, 33] 14 print(c) # [11, 22, 33] 15 print(d) # [11, 22, 33]
在上面的代碼中我們引入了一個 copy 的模塊,引入模塊這個之前的文章沒有提到,后續會單獨說一下。
在 copy 模塊中有一個 deepcopy() 的方法,從上面的代碼中可以看出 deepcopy() 的方法就相當於 c=[11,22,33] 這樣重新開辟一塊區域來存儲變量,當 a 變量改變時,淺拷貝的內容 b 會隨之改變,但是深拷貝的內容 d 並不是指向 a 的內存地址,所以不會改變。
在 copy 的模塊中還有一個 copy() 方法,它在某些方面看起來和 deepcopy() 方法是一樣的,其實不一樣,如下:
import copy a = [11, 22, 33] b = copy.copy(a) c = copy.deepcopy(a) a[0] = 123 print(a) # [123, 22, 33] print(b) # [11, 22, 33] print(c) # [11, 22, 33]
從上面的輸出結果看,當 a 變量修改之后,b 變量和 c 變量一樣沒有改變,那 copy() 和 deepcopy() 到底有什么區別呢,我們再看下面的代碼:
1 import copy 2 3 a = [11, 22, [123,456]] 4 b = copy.copy(a) 5 c = copy.deepcopy(a) 6 a[2][0] = 789 7 print(a) # [11, 22, [789, 456]] 8 print(b) # [11, 22, [789, 456]] 9 print(c) # [11, 22, [123, 456]]
從上面的輸出結果可以看出,b 變量隨 a 變量的變化而變化了,而 c 變量沒有。
很顯然這時 copy() 函數拷貝的值隨着原對象的值修改了,而 deepcopy() 的值沒有隨着原對象的值修改。主要是因為 deepcopy() 會將復雜對象的每一層復制一個單獨的個體出來對於 copy() 函數要慎用,慎用。