對于淺拷貝(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