Python深拷貝和淺拷貝詳解

對于淺拷貝(shallow copy)和深度拷貝(deep copy),本節(jié)并不打算一上來拋出它們的概念描孟,而是先從它們的操作方法說起驶睦,通過代碼來理解兩者的不同。

Python淺拷貝

常見的淺拷貝的方法匿醒,是使用數據類型本身的構造器场航,比如下面兩個例子:

1.  list1 = [1, 2, 3]
2.  list2 = list(list1)
3.  print(list2)
4.  print("list1==list2 ?",list1==list2)
5.  print("list1 is list2 ?",list1 is list2)

7.  set1= set([1, 2, 3])
8.  set2 = set(set1)
9.  print(set2)
10.  print("set1==set2 ?",set1==set2)
11.  print("set1 is set2 ?",set1 is set2)

運行結果為:

[1, 2, 3]
list1==list2 ? True
list1 is list2 ? False
{1, 2, 3}
set1==set2 ? True
set1 is set2 ? False

在上面程序中,list2 就是 list1 的淺拷貝廉羔,同理 set2 是 set1 的淺拷貝溉痢。

當然,對于可變的序列蜜另,還可以通過切片操作符“:”來完成淺拷貝适室,例如:

1.  list1 = [1, 2, 3]
2.  list2 = list1[:]
3.  print(list2)
4.  print("list1 == list2 ?",list1 == list2)
5.  print("list1 is list2 ?",list1 is list2)

運行結果為:

[1, 2, 3]
list1 == list2 ? True
list1 is list2 ? False

除此之外,Python 還提供了對應的函數 copy.copy() 函數举瑰,適用于任何數據類型捣辆。其用法如下:

1.  import copy
2.  list1 = [1, 2, 3]
3.  list2 = copy.copy(list1)
4.  print(list2)
5.  print("list1 == list2 ?",list1 == list2)
6.  print("list1 is list2 ?",list1 is list2)

運行結果為:

[1, 2, 3]
list1 == list2 ? True
list1 is list2 ? False

不過需要注意的是,對于元組此迅,使用 tuple() 或者切片操作符 ':' 不會創(chuàng)建一份淺拷貝汽畴,相反它會返回一個指向相同元組的引用:

1.  tuple1 = (1, 2, 3)
2.  tuple2 = tuple(tuple1)
3.  print(tuple2)
4.  print("tuple1 == tuple2 ?",tuple1 == tuple2)
5.  print("tuple1 is tuple2 ?",tuple1 is tuple2)

運行結果為:

(1, 2, 3)
tuple1 == tuple2 ? True
tuple1 is tuple2 ? True

此程序中旧巾,元組 (1, 2, 3) 只被創(chuàng)建一次,t1 和 t2 同時指向這個元組忍些。

看到這里鲁猩,也許你可能對淺拷貝有了初步的認識。淺拷貝罢坝,指的是重新分配一塊內存廓握,創(chuàng)建一個新的對象,但里面的元素是原對象中各個子對象的引用嘁酿。

對數據采用淺拷貝的方式時隙券,如果原對象中的元素不可變,那倒無所謂闹司;但如果元素可變娱仔,淺拷貝通常會出現一些問題,例如:

1.  list1 = [[1, 2], (30, 40)]
2.  list2 = list(list1)

4.  list1.append(100)
5.  print("list1:",list1)
6.  print("list2:",list2)

8.  list1[0].append(3)
9.  print("list1:",list1)
10.  print("list2:",list2)

12.  list1[1] += (50, 60)
13.  print("list1:",list1)
14.  print("list2:",list2)

運行結果為:

list1: [[1, 2], (30, 40), 100]
list2: [[1, 2], (30, 40)]
list1: [[1, 2, 3], (30, 40), 100]
list2: [[1, 2, 3], (30, 40)]
list1: [[1, 2, 3], (30, 40, 50, 60), 100]
list2: [[1, 2, 3], (30, 40)]

此程序中游桩,首先初始化了 list1 列表牲迫,包含一個列表和一個元組;然后對 list1 執(zhí)行淺拷貝借卧,賦予 list2盹憎。因為淺拷貝里的元素是對原對象元素的引用,因此 list2 中的元素和 list1 指向同一個列表和元組對象谓娃。

接著往下看脚乡,list1.append(100) 表示對 list1 的列表新增元素 100。這個操作不會對 list2 產生任何影響滨达,因為 list2 和 list1 作為整體是兩個不同的對象,并不共享內存地址俯艰。操作過后 list2 不變捡遍,list1 會發(fā)生改變。

再來看竹握,list1[0].append(3) 表示對 list1 中的第一個列表新增元素 3画株。因為 list2 是 list1 的淺拷貝,list2 中的第一個元素和 list1 中的第一個元素啦辐,共同指向同一個列表谓传,因此 list2 中的第一個列表也會相對應的新增元素 3。

最后是 list1[1] += (50, 60)芹关,因為元組是不可變的续挟,這里表示對 list1 中的第二個元組拼接,然后重新創(chuàng)建了一個新元組作為 list1 中的第二個元素侥衬,而 list2 中沒有引用新元組诗祸,因此 list2 并不受影響跑芳。

通過這個例子,你可以很清楚地看到使用淺拷貝可能帶來的副作用直颅。如果想避免這種副作用博个,完整地拷貝一個對象,就需要使用深拷貝功偿。所謂深拷貝盆佣,是指重新分配一塊內存,創(chuàng)建一個新的對象械荷,并且將原對象中的元素罪塔,以遞歸的方式,通過創(chuàng)建新的子對象拷貝到新對象中养葵。因此征堪,新對象和原對象沒有任何關聯关拒。

Python 中以 copy.deepcopy() 來實現對象的深度拷貝佃蚜。比如上述例子寫成下面的形式,就是深度拷貝:

1.  import copy
2.  list1 = [[1, 2], (30, 40)]
3.  list2 = copy.deepcopy(list1)

5.  list1.append(100)
6.  print("list1:",list1)
7.  print("list2:",list2)

9.  list1[0].append(3)
10.  print("list1:",list1)
11.  print("list2:",list2)

13.  list1[1] += (50, 60)
14.  print("list1:",list1)
15.  print("list2:",list2)

運行結果為:

list1: [[1, 2], (30, 40), 100]
list2: [[1, 2], (30, 40)]
list1: [[1, 2, 3], (30, 40), 100]
list2: [[1, 2], (30, 40)]
list1: [[1, 2, 3], (30, 40, 50, 60), 100]
list2: [[1, 2], (30, 40)]

可以看到着绊,無論 list1 如何變化剧包,list2 都不變。因為此時的 list1 和 list2 完全獨立疆液,沒有任何聯系一铅。

不過,深度拷貝也不是完美的堕油,往往也會帶來一系列問題潘飘。如果被拷貝對象中存在指向自身的引用,那么程序很容易陷入無限循環(huán)掉缺,例如:

1.  import copy
2.  list1 = [1]
3.  list1.append(list1)
4.  print(list1)

6.  list2 = copy.deepcopy(list1)
7.  print(list2)

運行結果為:

[1, [...]]
[1, [...]]

此例子中卜录,列表 x 中有指向自身的引用,因此 x 是一個無限嵌套的列表眶明。但是當深度拷貝 x 到 y 后艰毒,程序并沒有出現棧溢出的現象。這是為什么呢赘来?

其實现喳,這是因為深度拷貝函數 deepcopy 中會維護一個字典凯傲,記錄已經拷貝的對象與其 ID∴吕椋拷貝過程中冰单,如果字典里已經存儲了將要拷貝的對象,則會從字典直接返回灸促。通過查看 deepcopy 函數實現的源碼就會明白:

1.  def deepcopy(x, memo=None, _nil=[]):
2.  """Deep copy operation on arbitrary Python objects.

4.  See the module's __doc__ string for more info.
5.  """

7.  if memo is None:
8.  memo = {}
9.  d = id(x) # 查詢被拷貝對象 x 的 id
10.  y = memo.get(d, _nil) # 查詢字典里是否已經存儲了該對象
11.  if y is not _nil:
12.  return y # 如果字典里已經存儲了將要拷貝的對象诫欠,則直接返回
13.  ...

筆記來源C語言中文網 http://c.biancheng.net/view/4186.html

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浴栽,隨后出現的幾起案子荒叼,更是在濱河造成了極大的恐慌,老刑警劉巖典鸡,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件被廓,死亡現場離奇詭異,居然都是意外死亡萝玷,警方通過查閱死者的電腦和手機嫁乘,發(fā)現死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來球碉,“玉大人蜓斧,你說我怎么就攤上這事≌龆” “怎么了挎春?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長豆拨。 經常有香客問我直奋,道長,這世上最難降的妖魔是什么辽装? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任帮碰,我火速辦了婚禮,結果婚禮上拾积,老公的妹妹穿的比我還像新娘。我一直安慰自己丰涉,他們只是感情好拓巧,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著一死,像睡著了一般肛度。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上投慈,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天承耿,我揣著相機與錄音冠骄,去河邊找鬼。 笑死加袋,一個胖子當著我的面吹牛凛辣,可吹牛的內容都是我干的。 我是一名探鬼主播职烧,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扁誓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蚀之?” 一聲冷哼從身側響起蝗敢,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎足删,沒想到半個月后寿谴,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡失受,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年讶泰,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贱纠。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡峻厚,死狀恐怖,靈堂內的尸體忽然破棺而出谆焊,到底是詐尸還是另有隱情惠桃,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布辖试,位于F島的核電站辜王,受9級特大地震影響,放射性物質發(fā)生泄漏罐孝。R本人自食惡果不足惜呐馆,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望莲兢。 院中可真熱鬧汹来,春花似錦、人聲如沸改艇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谒兄。三九已至摔桦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間承疲,已是汗流浹背邻耕。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工鸥咖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兄世。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓啼辣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親碘饼。 傳聞我的和親對象是個殘疾皇子熙兔,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355