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