寫在之前
在昨天的文章裡 (零基礎學習 Python 之字典),寫字典的方法的時候留了一個小尾巴,那就是 copy() 方法還沒講。一是因為 copy 這個方法比較特殊,不單單是它表面的意思;二是以為昨天的文章寫得比較長,可能你看到那的時候就沒啥耐心去仔細思考了,但是這個知識點又比較重要,也是面試過程中會被長問起的題,我之前在面試的時候(乾貨滿滿--親身經歷的 Python 面試題)就被問起過。所以我把 copy 單獨摘出來今天單講。
正式開始
首先我在這介紹兩個新的小知識,要在下面用到。一個是函數 id() ,另一個是運算符 is。id() 函數就是返回對象的內存地址;is 是比較兩個變量的對象引用是否指向同一個對象,在這裡請不要和 == 混了,== 是比較兩個變量的值是否相等。
>>> a = [1,2,3]>>> b = [1,2,3]>>> id(a)38884552L>>> a is bFalse>>> a == bTrue
copy 這個詞有兩種叫法,一種是根據它的發音音譯過來的,叫拷貝;另一種就是標準的翻譯,叫複製。
其實單從表面意思來說,copy 就是將某件東西再複製一份,但是在很多程式語言中,比如 Python,C++中,它就不是那麼的簡單了。
>>> a = 1>>> b = a>>> b1
看到上面的例子,從表面上看我們似乎是得到了兩個 1,但是如果你看過我之前寫的文章,你應該對一句話有印象,那就是 「變量無類型」, Python 中變量就是一個標籤,這裡我們有請 id() 閃亮登場,看看它們在內存中的位置。
>>> a = 1>>> b = a>>> b1>>> id(a)31096808L>>> id(b)31096808L
看出來了嗎,id(a) 和 id(b) 相等,所以並沒有兩個 1,只是一個 1 而已,只不過是在 1 上貼了兩張標籤,名字是 a 和 b 罷了,這種現象普遍存在於 Python 之中,這種賦值的方式實現了 「假裝」 拷貝,真實的情況還是兩個變量和同一個對象之間的引用關係。
我們再來看 copy() 方法:
>>> a = {'name':'rocky','like':'python'}
>>> b = a.copy()>>> b{'name': 'rocky', 'like': 'python'}>>> id(a)31036280L>>> id(b)38786728L
咦,果然這次得到的 b 和原來的 a 不同,它是在內存中又開闢了一個空間。那麼我們這個時候就來推理了,雖然它們兩個是一樣的,但是它們在兩個不同的內存空間裡,那麼肯定彼此互不幹擾,如果我們去把 b 改了,那麼 a 肯定不變。
>>> b['name'] = 'leey'
>>> b{'name': 'leey', 'like': 'python'}>>> a{'name': 'rocky', 'like': 'python'}
結果和我們上面推理的一模一樣,所以理解了對象有類型,變量無類型,變量是對象的標籤,就能正確推斷出 Python 提供的結果。
我們接下來在看一個例子,請你在往下看的時候保證上面的你已經懂了,不然容易暈車。
>>> a = {'name':'rocky','like':'python'}
>>> b = a>>> b{'name': 'rocky', 'like': 'python'}>>> b['name'] = 'leey'>>> b{'name': 'leey', 'like': 'python'}>>> a{'name': 'leey', 'like': 'python'}
上面的例子看出什麼來了嗎?修改了 b 對應的字典類型的對象,a 的對象也變了。也就是說, b = a 得到的結果是兩個變量引用了同一個對象,但是事情真的這麼簡單嗎?請睜大你的眼睛往下看,重點來了。
>>> first = {'name':'rocky','lanaguage':['python','c++','java']}
>>> second = first.copy()>>> second{'name': 'rocky', 'lanaguage': ['python', 'c++', 'java']}>>> id(first)31036280L>>> id(second)38786728L
在這裡的話沒有問題,和我們之前說的一樣,second 是從 first 拷貝過來的,它們分別引用的是兩個對象。
>>> second['lanaguage'].remove('java')
>>> second{'name': 'rocky', 'lanaguage': ['python', 'c++']}>>> first{'name': 'rocky', 'lanaguage': ['python', 'c++']}
發現什麼了嗎?按理說上述例子中 second 的 lanaguage 對應的是一個列表,我刪除這個列表裡的值,也只應該改變的是 second 啊,為什麼連 first 的也會改,不是應該互不幹擾嗎?是不是很意外?是我們之前說的不對嗎?那我們再試試另一個鍵:
>>> second['name'] = 'leey'
>>> second{'name': 'leey', 'lanaguage': ['python', 'c++']}>>> first{'name': 'rocky', 'lanaguage': ['python', 'c++']}
前面說的原理是有效的,那這到底是為什麼啊,來來來,有請我們的 id() 再次閃亮登場。
>>> id(first['name'])38829152L>>> id(second['name'])38817544L>>> id(first['lanaguage'])38754120L>>> id(second['lanaguage'])38754120L
其實這裡深層次的原因是和 Python 的存儲數據的方式有關,這裡不做過多的說明(其實是我也不懂。。 在這裡,我們只需要知道的是,當 copy() 的時候,列表這類由字符串,數字等複合而成的對象仍然是複製了引用,也就是貼標籤,並沒有建立一個新的對象,我們把這種拷貝方式叫做淺拷貝(唉呀媽呀,終於把這個概念引出來了。。,言外之意就是並沒有解決深層次的問題,再言外之意就是還有能夠解決深層次問題的方法。
確實,在 Python 中還有一個深拷貝(deep copy),在使用它之前要引入一個 copy 模塊,我們來試一下。
>>> import copy>>> first = {'name':'rocky','lanaguage':['python','c++','java']}>>> second = copy.deepcopy(first)>>> second{'name': 'rocky', 'lanaguage': ['python', 'c++', 'java']}>>> second['lanaguage'].remove('java')>>> second{'name': 'rocky', 'lanaguage': ['python', 'c++']}>>> first{'name': 'rocky', 'lanaguage': ['python', 'c++', 'java']}
用了深拷貝以後,果然就不是引用了。
寫在最後
深拷貝和淺拷貝到這裡就講完了,花了一番功夫總算寫的還令自己滿意,不知道朋友們看到這裡的時候是否是覺得對這一部分豁然開朗,我盡力了。這個拓展也可能是成為一個系列,補充一些我覺得理解起來比較困難或者平時面試求職或者工作中常見的知識點,希望您多捧場。
最後感謝你能看到這裡,希望我寫的東西能夠讓你有到收穫,但是我還是希望我在文章裡插入的代碼,你們能自己動手試一下,都很簡單。原創不易,每一個字,每一個標點都是自己手敲的,所以希望大家能多給點支持,該關注關注,該點讚點讚,該轉發轉發,有什麼問題歡迎在後臺聯繫我,也可以在公眾號找到我的微信加我。
我這麼認真你還不趕緊走一波關注嗎??
The end。