以前關於 Python 的做用域、賦值、參數傳遞,咱們接連談了幾篇文章:編程
今天咱們依然要就相關話題繼續下去。app
首先是上次最後的思考題:編輯器
m = [1, 2, [3]] n = m[:] n[1] = 4 n[2][0] = 5 print(m)
m 的結果是什麼?函數
正確答案是 [1, 2, [5]] ,此次比上次好點,有 35% 的正確率。人工智能
當時我留了個提示,說和淺拷貝、深拷貝有關,如今咱們就來具體說一說。spa
假設有這樣一個 list 變量 m,其中有 4 個元素(別被嵌套迷惑了):3d
m = [1, 2, [3, 4], [5, [6, 7]]]
爲了更直觀的表示,我來畫個圖:code
如今咱們想要再來「複製」一個一樣的變量。也許第一個閃過腦中的念頭就是:對象
n = m
但看了前面的文章後你應該知道,這樣的 賦值只至關於增長了一個標籤,並無新的對象產生 :blog
用 id
驗證下就知道, m 和 n 仍然是同一個東西 。那麼他們內部的元素天然也是同樣的,對其中一個進行修改,另外一個也會跟着變:
m = [1, 2, [3, 4], [5, [6, 7]]] print('m:', id(m)) print([id(i) for i in m]) n = m print('n:', id(n)) print([id(i) for i in n]) print(n is m) print(n[0] is m[0]) print(n[2] is m[2]) n[0] = -1 print(m) n[2][1] = -1 print(m)
輸出
m: 4564554888 [4556507504, 4556507536, 4564554760, 4564555016] n: 4564554888 [4556507504, 4556507536, 4564554760, 4564555016] True True True [-1, 2, [3, 4], [5, [6, 7]]] [-1, 2, [3, -1], [5, [6, 7]]]
所以有人將此操做稱爲「 舊瓶裝舊酒 」,只是多貼了一層標籤,這不能達到咱們的目的。要獲得一個對象的「拷貝」,咱們須要用到 copy
方法:
from copy import copy m = [1, 2, [3, 4], [5, [6, 7]]] print('m:', id(m)) print([id(i) for i in m]) n = copy(m) print('n:', id(n)) print([id(i) for i in n]) print(n is m) print(n[0] is m[0]) print(n[2] is m[2]) n[0] = -1 print(m) n[2][1] = -1 print(m)
輸出
m: 4340253832 [4333009264, 4333009296, 4340253704, 4340253960] n: 4340268104 [4333009264, 4333009296, 4340253704, 4340253960] False True True [1, 2, [3, 4], [5, [6, 7]]] [1, 2, [3, -1], [5, [6, 7]]]
從結果中能夠看出, n 和 m 已不是同一個對象 ,對於某個元素的從新賦值不會影響原對象。可是,它們 內部的元素全都是同樣的 ,因此對一個可變類型元素的修改,則仍然會反應在原對象中。
(其實這裏一、2也是指向同一個對象,但做爲不可變對象來講,它們互不影響,直觀上的感覺就至關因而複製了一份,故簡化如圖上所示)
這種複製方法叫作 淺拷貝 ( shallow copy ),又被人形象地稱做「 新瓶裝舊酒 」,雖然產生了新對象,但裏面的內容仍是來自同一份。
若是要完全地產生一個和原對象徹底獨立的複製品,得使用 深拷貝 ( deep copy ):
from copy import deepcopy m = [1, 2, [3, 4], [5, [6, 7]]] print('m:', id(m)) print([id(i) for i in m]) n = deepcopy(m) print('n:', id(n)) print([id(i) for i in n]) print(n is m) print(n[0] is m[0]) print(n[2] is m[2]) n[0] = -1 print(m) n[2][1] = -1 print(m)
輸出
m: 4389131400 [4381886832, 4381886864, 4389131272, 4389131528] n: 4389131208 [4381886832, 4381886864, 4389131656, 4389145736] False True False [1, 2, [3, 4], [5, [6, 7]]] [1, 2, [3, 4], [5, [6, 7]]]
此時, 對新對象中元素作任何改動都不會影響原對象 。新對象中的子列表,不管有多少層,都是新的對象,有不一樣的地址。
按照前面的比喻,深拷貝就是「 新瓶裝新酒 」。
你可能會注意到一個細節:n 中的前兩個元素的地址仍然和 m 中同樣。這是因爲它們是 不可變對象,不存在被修改的可能,因此拷貝和賦值是同樣的 。
因而,深拷貝也能夠理解爲,不只是對象自身的拷貝,並且對於對象中的每個子元素,也都進行一樣的拷貝操做。這是一種 遞歸 的思想。
不過額外要說提醒一下的是, 深拷貝的實現過程並非徹底的遞歸 ,不然若是對象的某級子元素是它自身的話,這個過程就死循環了。實際上, 若是遇到已經處理過的對象,就會直接使用其引用,而再也不重複處理 。聽上去有點難懂是否是?想一想這個例子大概就會理解了:
from copy import deepcopy m = [1, 2] m.append(m) print(m, id(m), id(m[2])) n = deepcopy(m) print(n, id(n), id(n[2]))
輸出
[1, 2, [...]] 4479589576 4479589576 [1, 2, [...]] 4479575048 4479575048
最後,仍是給各位留個思考:
from copy import deepcopy a = [3, 4] m = [1, 2, a, [5, a]] n = deepcopy(m) n[3][1][0] = -1 print(n)
深拷貝後的 n,修改了其中一個元素值,會是怎樣的效果?
思考一下輸出會是什麼?
而後本身在電腦上或者咱們的在線編輯器 Crossin的編程教室 - 在線Python編輯器 裏輸入代碼運行下看看結果,再想一想爲何。
歡迎留言給出你的解釋。
════
其餘文章及回答:
如何自學Python | 新手引導 | 精選Python問答 | Python單詞表 | 人工智能 | 嘻哈 | 爬蟲 | 我用Python | 高考 | requests | AI平臺
歡迎搜索及關注: Crossin的編程教室