Python深拷貝和淺拷貝詳解

2021-02-19 小學生編程

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

Python淺拷貝

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

list1 = [1, 2, 3]list2 = list(list1)print(list2)print("list1==list2 ?",list1==list2)print("list1 is list2 ?",list1 is list2)set1= set([1, 2, 3])set2 = set(set1)print(set2)print("set1==set2 ?",set1==set2)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 的淺拷貝。

當然,對於可變的序列,還可以通過切片操作符「:」來完成淺拷貝,例如:

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

運行結果為:

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


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

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

運行結果為:

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


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

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

運行結果為:

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

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

看到這裡,也許你可能對淺拷貝有了初步的認識。淺拷貝,指的是重新分配一塊內存,創建一個新的對象,但裡面的元素是原對象中各個子對象的引用。

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

list1 = [[1, 2], (30, 40)]list2 = list(list1)list1.append(100)print("list1:",list1)print("list2:",list2)list1[0].append(3)print("list1:",list1)print("list2:",list2)list1[1] += (50, 60)print("list1:",list1)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 執行淺拷貝,賦予 list2。因為淺拷貝裡的元素是對原對象元素的引用,因此 list2 中的元素和 list1 指向同一個列表和元組對象。

接著往下看,list1.append(100) 表示對 list1 的列表新增元素 100。這個操作不會對 list2 產生任何影響,因為 list2 和 list1 作為整體是兩個不同的對象,並不共享內存地址。操作過後 list2 不變,list1 會發生改變。

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

最後是 list1[1] += (50, 60),因為元組是不可變的,這裡表示對 list1 中的第二個元組拼接,然後重新創建了一個新元組作為 list1 中的第二個元素,而 list2 中沒有引用新元組,因此 list2 並不受影響。

通過這個例子,你可以很清楚地看到使用淺拷貝可能帶來的副作用。如果想避免這種副作用,完整地拷貝一個對象,就需要使用深拷貝。所謂深拷貝,是指重新分配一塊內存,創建一個新的對象,並且將原對象中的元素,以遞歸的方式,通過創建新的子對象拷貝到新對象中。因此,新對象和原對象沒有任何關聯。

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

import copylist1 = [[1, 2], (30, 40)]list2 = copy.deepcopy(list1)list1.append(100)print("list1:",list1)print("list2:",list2)list1[0].append(3)print("list1:",list1)print("list2:",list2)list1[1] += (50, 60)print("list1:",list1)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 完全獨立,沒有任何聯繫。

不過,深度拷貝也不是完美的,往往也會帶來一系列問題。如果被拷貝對象中存在指向自身的引用,那麼程序很容易陷入無限循環,例如:

import copylist1 = [1]list1.append(list1)print(list1)list2 = copy.deepcopy(list1)print(list2)

運行結果為:

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

此例子中,列表 x 中有指向自身的引用,因此 x 是一個無限嵌套的列表。但是當深度拷貝 x 到 y 後,程序並沒有出現棧溢出的現象。這是為什麼呢?

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

def deepcopy(x, memo=None, _nil=[]):    """Deep copy operation on arbitrary Python objects.           See the module's __doc__ string for more info.    """       if memo is None:        memo = {}    d = id(x)     y = memo.get(d, _nil)     if y is not _nil:        return y         ...

相關焦點

  • 面試題-python 淺拷貝和深拷貝(copy模塊)
    前言面試的時候經常會問到深拷貝和淺拷貝,那麼python的深拷貝和淺拷貝有什麼區別呢?這裡有個知識點:在python中,都是將「對象的引用(內存地址)」賦值給變量的。其次,在python中有6個標準數據類型,他們分為可變和不可變兩類。可變和不可變對象在python中有6個標準數據類型,他們分為可變和不可變兩類。
  • 深拷貝和淺拷貝之list、dataframe
    python list:b = a是淺拷貝,b = list(a)和b = copy.cpoy(a)是深拷貝。淺拷貝,a和b指向的是一個地址。當b改變後,a也會改變。深拷貝,a和b指向的是兩個地址,當b改變後,a不受影響。
  • 前端面試-深拷貝和淺拷貝
    面試題目:如何實現對一個數組或對象的淺拷貝和深拷貝?WTF,複製還分兩種,第一次遇到這種問題的時候很是無語呢,先來看看一般的答案的理解。淺拷貝是只拷貝一層,深層次的對象級別就只拷貝引用。 深拷貝是拷貝多層,每一級別的數據都拷貝出來。
  • 5 張圖徹底理解 Python 中的淺拷貝與深拷貝
    假設你去面試 Python 開發崗,面試官如果對基礎比較看重的話,那麼很可能會問你這樣的問題「談談你對 Python 中的淺拷貝和深拷貝的理解
  • JavaScript系列--如何優雅簡單的實現深拷貝和淺拷貝
    優雅簡單的實現深拷貝和淺拷貝淺析JavaScript解析賦值、淺拷貝和深拷貝的區別:裡面介紹了解析賦值,淺拷貝,深拷貝的原理和實現。淺拷貝方法:Object.assign(),展開語法Spread,Array.prototype.alice(),array.prototype.concat()。深拷貝方法:JSON.parse(JSON.stringify(object)),對於undefined,symbol和函數的會直接忽略。
  • 一文讀懂 javascript 深拷貝與淺拷貝
    讀完本文,希望你能明白:淺拷貝與深拷貝淺拷貝是創建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內存地址 ,所以如果其中一個對象改變了這個地址,就會影響到另一個對象。
  • 低門檻徹底理解JavaScript中的深拷貝和淺拷貝
    在說深拷貝與淺拷貝前,我們先看兩個簡單的案例:按照常規思維,obj1應該和num1一樣,不會因為另外一個值的改變而改變,而這裡的obj1 卻隨著obj2的改變而改變了。同樣是變量,為什麼表現不一樣呢?案例2中的賦值就是典型的淺拷貝,並且深拷貝與淺拷貝的概念只存在於引用類型。深拷貝與淺拷貝既然已經知道了深拷貝與淺拷貝的來由,那麼該如何實現深拷貝?
  • 圖解 Python 中深淺拷貝(copy)
    但是python對深copy做了一個優化,將可變的數據類型在內存中重新創建一份,而不可變的數據類型則沿用之前的,所以內存中是下面這樣的:小結:深copy:會在內存中開闢新空間,將原列表以及列表裡面的可變數據類型重新創建一份,不可變數據類型則沿用之前的。為什麼Python默認的拷貝方式是淺拷貝?
  • C++之拷貝構造函數的淺copy和深copy
    一、深拷貝和淺拷貝構造函數總結:1、兩個特殊的構造函數:(1)無參構造函數::(1)淺拷貝拷貝後對象的物理狀態相同(2)深拷貝拷貝後對象的邏輯狀態相同5、什麼時候需要進行深拷貝?(也就是我們自己手寫的),必然需要實現深拷貝!
  • JavaScript的深拷貝實現
    在實際開發當中,我們經常會遇到要對對象進行深拷貝的情況。而且深拷貝這個問題在面試過程中也經常會遇到,下面就對本人在學習過程中的收穫,做以簡單的總結。什麼是淺拷貝,什麼是深拷貝?什麼是淺拷貝關於淺拷貝的概念,我在網上看到一種說法,直接上代碼。
  • 看看你知道的「淺拷貝」是對的嗎
    她: 「複製對象有深拷貝和淺拷貝...」大佬:」說一下這兩者之間的區別「她: 」我給你寫一段淺拷貝的代碼「var a = { x: 1 };var b = a;大佬:」回去等通知吧 ~.~「2. 第二種定義:一個新的對象直接拷貝已存在的對象的對象屬性的引用,即淺拷貝。第一種和第二種的差異即是,對象本身引入與對象的對象屬性的引入,因為我沒有找到標準的關於淺拷貝的官方定義,所以對於這兩種方式便開始思考想辦法去驗證。
  • 一篇文章讀懂Python賦值與拷貝
    拷貝又分為淺拷貝和深拷貝。>>> s = [1,2,3]>>> sc = copy.copy(s) # 淺拷貝>>> sc[1, 2, 3]>>> sdc = copy.deepcopy(s)  >>> sdc[1, 2, 3]拷貝出來的對象只是值相同,實為不同的對象
  • 深淺拷貝知多少?
    基礎概念淺拷貝概念:只複製指向某個對象的指針,而不複製對象本身,新舊對象還是共享同一塊內存深拷貝實現方式淺拷貝的實現方式 以上了解了數據類型的存儲和拷貝的基礎概念,來具體看一下哪一些情況,賦值、拷貝是可以完全獨立存在的,哪一些情況是會改變到原有對象的內容的。請看:按照淺拷貝的基礎概念,寫了一個shallowCopy的方法,並且拷貝後同樣改變原數據中的元素,如下:
  • 面試題:如何實現一個深拷貝
    今天這篇文章我們來看看一道必會面試題,即如何實現一個深拷貝。第一步:簡單實現其實深拷貝可以拆分成 2 步,淺拷貝 + 遞歸,淺拷貝時判斷屬性值是否是對象,如果是對象就進行遞歸操作,兩個一結合就實現了深拷貝。根據上篇文章內容,我們可以寫出簡單淺拷貝代碼如下。
  • Pandas和Numpy的視圖和拷貝
    在Numpy和Pandas中,有兩個重要概念,容易混淆,一個是淺拷貝,也稱為視圖,另外一個是深拷貝,或者就稱為拷貝。如果操作不當,Pandas會爆出SettingWithCopyWarning的異常。本文我將就視圖和拷貝問題,結合異常進行總結。
  • 深拷貝的終極探索(90%的人都不了解)
    ,由淺入深,環環相扣,總共涉及4種深拷貝方式,每種方式都有自己的特點和個性深拷貝 VS 淺拷貝再開始之前需要先給同學科普下什麼是深拷貝,和深拷貝有關係的另個一術語是淺拷貝又是什麼意思呢?如果對這部分部分內容了解的同學可以跳過其實深拷貝和淺拷貝都是針對的引用類型,JS中的變量類型分為值類型(基本類型)和引用類型;對值類型進行複製操作會對值進行一份拷貝,而對引用類型賦值,則會進行地址的拷貝,最終兩個變量指向同一份數據// 基本類型var a = 1;var b = a;a = 2;console.log(a, b); // 2, 1 ,a b指向不同的數據
  • 什麼是Java深淺拷貝?
    拷貝與Java內存結構息息相關,搞懂Java深淺拷貝是很必要的!在對象的拷貝中,很多初學者可能搞不清到底是拷貝了引用還是拷貝了對象。在拷貝中這裡就分為引用拷貝、淺拷貝、深拷貝進行講述。引用拷貝引用拷貝會生成一個新的對象引用地址,但是兩個最終指向依然是同一個對象。如何更好的理解引用拷貝呢?
  • Linux 拷貝、剪切、粘貼等常用命令詳解
    Linux 拷貝、剪切、粘貼等常用命令詳解複製粘貼命令:在一行的任何位置按下yy,y是yanked拷貝的意思,然後去想粘貼的位置按下p即可。p是粘貼的意思。如果想複製3行的話,按下3yy,就複製3行,如果想複製多行的話,直接按數字可以選中多好,然後粘貼。
  • Java 拷貝,你能說出個 123 麼?
    作者 | sowhat1412 責編 | 張文頭圖 | CSDN 下載自視覺中國本文主要講解:對象創建方式、Java中的深拷貝和淺拷貝。本次講解的是 Java 的深拷貝和淺拷貝,其實現方式正是通過調用 Object 類的 clone() 方法來完成。
  • js基礎之數據類型和拷貝
    console.log(toString.apply(undefined));    // [object Undefined]console.log(toString.apply(null));         // [object Null]console.log(toString.apply(new Person));   // [object Object] 1.3 深拷貝和淺拷貝