Python的浅拷贝和深拷贝
相對于其他傳統編程語言,Python有一個比較奇怪的特性,即在復制對象時,有淺拷貝(shallow copy)和深拷貝(deep copy)兩種方式。
淺拷貝和深拷貝只和復合對象相關。復合對象指的是包含對象的對象,如列表(list)、類實例(class instance)等。簡單類型的對象(int、float、string等)不存在淺拷貝和深拷貝的說法。
看下面的實例:
colours1 = ["red", "blue"] colours2 = colours1 print(colours1) print(colours2) print(id(colours1), id(colours2))輸出結果:
['red', 'blue'] ['red', 'blue'] 563841065096 563841065096在上面的例子中,列表colours1被賦值給colours2。Colours1這樣的列表一般被稱為淺列表或普通列表,因為它只包含一些簡單數據類型,不包含嵌套結構,即不是嵌套列表。id()函數的值相同表明colours2和colours1這2個列表指向同一個對象,說明colours1被賦值給colours2時,并沒有分配新的內存地址,而是將colours2指向了colours1的內存地址。下圖給出了相關的數據結構說明。
現在我們看看分配一個新的列表對象給colours2,會發生什么?
輸出結果:
['red', 'blue'] ['rouge', 'vert'] 357368848712 357368848456跟我們期望的一樣,colours1的值保持不變,一個新的內存地址被分配給了colours2。
下面我們再看看colours2不是重新分配對象,而是改變其中一個元素的值,結果會有什么變化?
輸出結果:
1026592727752 1026592727752 1026592727752 1026592727752 ['red', 'green'] ['red', 'green']可以看到,當我們將colours2中的第二個元素重新賦值時,colours1中的值也被自動改變了,很多初學者在這里都非常迷惑。事實上我們并沒有分配一個新的對象給colours2。colours1和colours2仍然指向同一個列表對象。即我們沒有兩個列表,仍然只有1個,只不過有2個名字。
那對于簡單列表,有沒有完全拷貝的方案呢,有!那就是使用切片方法。因為切片方法是重新生成了一個新對象。
輸出結果:
['a', 'x', 'c', 'd'] ['a', 'b', 'c', 'd']但是,如果是像下面這樣的嵌套列表,就又會遇到新的困難和問題。因為切片操作本質上仍然是淺拷貝。當遇到嵌套列表時,切片方法只復制子列表的地址,而不是其全部內容。
lst1 = ['a', 'b', ['ab', 'ba']] lst2 = lst1[:]下面的圖給出了lst1和lst2的數據結構描述,lst2雖然是一個新建對象,但其中的子列表[‘ab’,’ba’]與lst1中的指的是同一個對象。
如果對lst1和lst2中的第一個元素或第二個元素進行賦值,并沒有什么副作用(side effect)
輸出結果:
['a', 'b', ['ab', 'ba']] ['c', 'b', ['ab', 'ba']]
但是,如果改變的是嵌套子列表中的值,那么情況就發生了變化。
輸出結果:
['a', 'b', ['ab', 'd']] ['c', 'b', ['ab', 'd']]下面的圖給出了為什么lst1中的嵌套子列表會跟隨lst2發生變化的原因,因為lst1和lst2的嵌套子列表指向同一個對象。
一個解決方案是使用標準庫的copy模塊。如果我們需要讓一個對象發生改變時不對原對象產生副作用,就需要一份這個對象的深度拷貝。深拷貝不僅僅拷貝了原始對象自身,也對其包含的值進行拷貝,它會遞歸的查找對象中包含的其他對象的引用,來完成更深層次拷貝。因此,深拷貝產生的副本可以隨意修改而不需要擔心會引起源對象的改變。
對先前的例子使用深拷貝:
from copy import deepcopy lst1 = ['a', 'b', ['ab', 'ba']] lst2 = deepcopy(lst1) print(lst1) print(lst2) print(id(lst1)) print(id(lst2)) print(id(lst1[0])) print(id(lst2[0])) print(id(lst1[2])) print(id(lst2[2]))輸出結果:
['a', 'b', ['ab', 'ba']] ['a', 'b', ['ab', 'ba']] 537508176136 537508176200 537506121184 537506121184 537508176712 537508176776可以看出lst1和lst2的嵌套子列表的內存地址不一樣了。下面的圖給出了deepcopy后的數據結構情形:
看看改變lst2中相關值的結果:
輸出結果:
['a', 'b', ['ab', 'ba']] ['c', 'b', ['ab', 'd']]上面的代碼最后將數據結構變成了這樣:
至于Python中為什么要有淺拷貝和深拷貝的區別,主要是出于效率方面的考慮。
參考文獻:http://www.python-course.eu/python3_deep_copy.php
總結
以上是生活随笔為你收集整理的Python的浅拷贝和深拷贝的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python如何在控制台显示进度条
- 下一篇: 高效使用Google