古靈精怪的python——地址,淺拷貝與身份運算子
首先丟擲一個問題,吸引讀者的閱讀興趣(如果您覺得這個不是問題,那麼這篇文章不適合您:)
請看如下程式碼:
>>> a = 3
>>> b = 3
>>> a == b
True
>>> a is b
True
>>> b = a
# 這沒問題
>>> a = 3
>>> a == b
# 這看起來也很合理
True
>>> a is b
True
>>> a is b
>>> a = (2,3)
>>> b = (2,3)
>>> b = a
False # ???why?
>>> a == b
True
>>> a is b
True
True # ???why?
>>> a == b
好了,整篇文章都是圍繞這個問題展開的。長久以來我都習慣用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
140602638349720
>>> c = a # a的引用複製給c,在記憶體中其實是指向了用一個物件
>>> id(c)
>>> a is b
True
True
>>> a is c
True
>>> b is c
我們看到,上面a,b,c的地址相同,所以他們互相之間”相同“
而相等則兩者之間的數值對應相等
>>> a = 3
>>> b = a
>>> b
>>> a = 4
3
>>> b = [3]
>>> a = [3]
>>> id(a)
4351374112
4351374184
>>> id(b)
>>> a is b
>>> a[0] = 4
False
>>> a == b
True
>>> b
[3]
>>> b = a # b就是a的引用,佔得是同一塊地址,而且當a的內容改變時,b也會隨之改變,這和上面
>>> a = [3]
# int物件不同,我也不知道為啥要這麼搞。
[4]
>>> a[0] = 4
>>> b
很多同學看到這肯定是一鍋漿糊了,其實就是一個原則,能用==就不用is。除了一種情況,那就是判斷物件是否是None。
>>> if a is None:
... pass
淺拷貝和深拷貝
>>> a = [3]
>>> b = a[:] #通過切片賦值,返回的是a的淺拷貝
>>> id(a)
4351273944
>>> id(b)
140602638349720
4351374184
>>> id(a[0])
>>> a is b
>>> id(b[0]) #list的地址不同
140602638349720
False
>>> a[0] is b[0] #淺拷貝,只拷貝了a的殼[],裡邊的內容仍然是同一個東西,同樣的id
True
>>> a == b
True
>>> a[0] == b[0]
True
>>> a[0] = 4 # 但是,b的內容不會隨著a的變化而變化
>>> b
[3]
淺拷貝拷貝了最外層容器,副本中的元素是原容器中元素的引用
我們再看一個例子
>>> Anndy = [`Anndy`, [`age`, 24]]
>>> Tom = Anndy[:]
>>> id(Anndy)
>>> Cindy = list(Anndy)
4351374040
4351374616
>>> id(Tom)
4351373968
>>> id(Cindy)
([`Anndy`, [`age`, 24]],[`Anndy`, [`age`, 24]],[`Anndy`, [`age`, 24]])
>>> print(Anndy, Tom, Cindy)
# 看起來是建立了三個不同的物件,因為他們的id各不相同
>>> print (Anndy, Tom, Cindy)
>>> Tom[0] = `Tom`
>>> Cindy[0] = `Cindy`
# 如果想修改某一個人的名字也沒有什麼問題
([`Anndy`, [`age`, 24]], [`Tom`, [`age`, 24]], [`Cindy`, [`age`, 24]])
# 現在我們想把Tom的年齡修改為12歲
>>> Tom[1][1] = 12
# 震驚!所有人的年齡都變成了12!!!
>>> print (Anndy, Tom, Cindy)
([`Anndy`, [`age`, 12]], [`Tom`, [`age`, 12]], [`Cindy`, [`age`, 12]])
>>> print ([id(x) for x in Anndy])
[4351366224, 4351374112] # 第一個姓名元素的地址不同,但是第二個列表是同一個
[4351366368, 4351374112] # 看第二個列表的地址
>>> print ([id(x) for x in Tom])
[4351323592, 4351374112] # 看第二個!
>>> print ([id(x) for x in Cindy])
構造方法或切片 [:] 做的是淺拷貝。如果所有元素都是不可變的(比如名字字串,修改的時候會重新建立物件,僅僅包括原子物件的元組也屬於這種情況),那麼這樣沒有問題,還能節省記憶體。但是,如果有可變的元素,可能就會導致意想不到的問題,正如剛剛,修改一個人的年齡,所有人的年齡都發生了變化。
所以,如果你想要深拷貝,應該這麼寫
>>> import copy
>>> Anndy = [`Anndy`, [`age`, 24]]
>>> Tom = copy.deepcopy(Anndy)
>>> print(Tom, Anndy)
>>> Tom[1][1] = 12
([`Anndy`, [`age`, 12]], [`Anndy`, [`age`, 24]]) #這樣寫就沒問題了
另外
不知道剛才你有沒有注意到
>>> a = 3
>>> b = 3
140602638349720
>>> id(a)
>>> id(b)
140602638349720 # 相同!
>>> a is b
True
Python會對比較小的整數物件進行快取快取起來。當整數比較大的時候就會重新開闢一塊記憶體。
>>> a = 999
>>> b = 999
140602638469952
>>> id(a)
>>> id(b)
>>> a is b
140602638469904 # 不同!
False
這僅僅是在命令列中執行,而在儲存為檔案執行,結果是不一樣的,這是因為直譯器做了一部分優化。
#!/usr/bin/env python
a = 3
b = 3
a = 99999
print(a is b)
b = 99999
True
print(a is b)
結果:
True
[Finished in 0.0s]
這也是為什麼我屢屢用is而不用==,程式執行良好的原因。
總結
1. python中,儘量不要用is
, 除非判斷物件是否為None
。
2. a is b
(相同)一定意味著a == b
(相等),而a == b
(相等) 不一定 a is b
(相同)這點比較好理解
3. 如果函式中傳參等,需要引用、拷貝的,注意是否生成了一個新的物件,即使生成了,內部元素是否是同一個物件的引用?尤其注意切片的使用。必要的時候用copy模組進行深拷貝而不要用切片這種淺拷貝形式。
原文釋出時間為:2018-08-12
本文作者:DeepWeaver
本文來自雲棲社群合作伙伴“Python愛好者社群”,瞭解相關資訊可以關注“Python愛好者社群”。
相關文章
- Python淺拷貝與深拷貝Python
- python深拷貝與淺拷貝Python
- 圖解 Python 淺拷貝與深拷貝圖解Python
- Python3之淺談----深拷貝與淺拷貝Python
- 深入淺出的“深拷貝與淺拷貝”
- 淺拷貝與深拷貝
- 深入淺出深拷貝與淺拷貝
- JS深拷貝與淺拷貝JS
- python 指標拷貝,淺拷貝和深拷貝Python指標
- 淺拷貝與深拷貝的實現
- 【JavaScript】物件的淺拷貝與深拷貝JavaScript物件
- JavaScript中的淺拷貝與深拷貝JavaScript
- 淺談深拷貝與淺拷貝?深拷貝幾種方法。
- python學習筆記–深拷貝與淺拷貝的區別Python筆記
- python身份運算子Python
- 談談深拷貝與淺拷貝
- 賦值、淺拷貝與深拷貝賦值
- 深拷貝、淺拷貝與Cloneable介面
- React之淺拷貝與深拷貝React
- 理解JS中的淺拷貝與深拷貝JS
- python深淺拷貝Python
- 5張圖徹底理解Python中的淺拷貝與深拷貝Python
- 淺拷貝與深拷貝程式碼(javascript)JavaScript
- ES6深拷貝與淺拷貝
- IOS學習之淺析深拷貝與淺拷貝iOS
- 【Python】直接賦值,深拷貝和淺拷貝Python賦值
- Python擴充套件_淺拷貝和深拷貝Python套件
- Python - 物件賦值、淺拷貝、深拷貝的區別Python物件賦值
- 【JS】深拷貝與淺拷貝,實現深拷貝的幾種方法JS
- 淺拷貝&深拷貝
- Java 輕鬆理解深拷貝與淺拷貝Java
- 三目運算、物件克隆、深拷貝和淺拷貝物件
- PHP 物件導向 - 物件的淺拷貝與深拷貝PHP物件
- python深拷貝和淺拷貝之簡單分析Python
- Python中列表的深淺拷貝Python
- js的深拷貝和淺拷貝JS
- 淺拷貝和深拷貝
- 深拷貝和淺拷貝