Python淺拷貝-深拷貝 解析

淺拷貝

copy.copy()

copy函數是淺拷貝,只對可變類型的第一層對象進行拷貝,對拷貝的對象開辟新的內存空間進行存儲,不會拷貝對象內部的子對象

特性: 淺拷貝只會對可變類型第一層進行拷貝;

  • 可變類型:只能作用于列表/字典/集合,而不能拷貝數字/字符串/元組;
  • 第一層:例如一個列表內的嵌套列表無法拷貝[1,2,[a,b],3]。
如何驗證?

通過id()對比拷貝前后對象的內存地址,或直接用is方法判斷拷貝前后對象內存地址是否相同。

驗證示例:
  • 字符串淺拷貝

    import copy
    a = "abc"
    b = copy.copy(a)
    print("a的內存地址:",id(a))
    print("b的內存地址:",id(b))
    

    輸出:

    a的內存地址: 2140603563288 
    b的內存地址: 2140603563288
    

    結論:未拷貝

  • 數值淺拷貝

    a = 12
    b = copy.copy(a)
    print("a的內存地址:",id(a))
    print("b的內存地址:",id(b))
    

    輸出:

    a的內存地址: 1896907216
    b的內存地址: 1896907216
    

    結論:未拷貝

  • 列表淺拷貝

    a = [1,2,3]
    b = copy.copy(a)
    print("a的內存地址:",id(a))
    print("b的內存地址:",id(b))
    

    輸出:

    a的內存地址: 2140688273224
    b的內存地址: 2140681134216
    

    結論:拷貝成功,創建了新的內存地址

  • 列表嵌套情況淺拷貝(特別注意)

    a = [1,2,[3,4]]
    b = copy.copy(a)
    print("a:",a)
    print("a的內存地址:",id(a))
    print("b:",b)
    print("b的內存地址:",id(b))
    

    輸出:

    a: [1, 2, [3, 4]]
    a的內存地址: 2140679959240
    b: [1, 2, [3, 4]]
    b的內存地址: 2140679959112
    

    外部地址改變,創建了新的內存地址

    驗證內部嵌套列表是否拷貝:

    In [43]: id(a[2])
    Out[43]: 2140680066952
    
    In [44]: id(b[2])
    Out[44]: 2140680066952
    

    結論:嵌套列表內存地址相同,未進行拷貝

    再次驗證,嘗試修改a列表,是否對b列表造成影響:

    In [45]: a.append(5)
    In [46]: a[2].append("a")
    In [48]: b
        
    Out[48]: [1, 2, [3, 4, 'a']]
    

    分析:a列表修改外層列表不影響b列表;修改a列表的子列表會同時改變b列表的子列表。

    結論

    符合淺拷貝的特性,淺拷貝只會對可變類型第一層進行拷貝,而不會拷貝其可變類型的子對象,因此未拷貝部分指向的仍是同一個內存地址;

深拷貝

copy.deepcopy

deepcopy函數是深拷貝, 只要發現對象有可變類型就會對該對象到最后一個可變類型的每一層對象就行拷貝, 對每一層拷貝的對象都會開辟新的內存空間進行存儲。

深拷貝同樣無法拷貝不可變類型:字符串、數字、元組。

  • 不可變類型的深拷貝(主要討論元組及其子元素)
# 不可變類型元組(需要特別注意)  
In [59]: a = (1,2)

In [60]: b = copy.deepcopy(a)

In [61]: id(a)
Out[61]: 2140688276424

In [62]: id(b)
Out[62]: 2140688276424
      
In [63]: a = (1,2,[1,3])

In [64]: b =copy.deepcopy(a)

    
# 此處雖然外層元組是不可變類型,但內存地址依然改變了,原因是深拷貝會對所有可變子對象進行拷貝,因此內部列表會被拷貝,內存地址改變
In [65]: id(a)
Out[65]: 2140685431720

In [66]: id(b)
Out[66]: 2140688106408
    
# 其內部的子列表被拷貝,內存地址改變,由于元組是不可變類型,內部改變,其本身地址也會改變。
In [67]: id(a[2])
Out[67]: 2140681195272

In [68]: id(b[2])
Out[68]: 2140687283336
# 其內部的不可變對象無法拷貝,內存地址不變
In [69]: id(a[1])
Out[69]: 1896906896

In [70]: id(b[1])
Out[70]: 1896906896
  

? 結論:

? 不可變類型進行深拷貝如果子對象沒有可變類型則不會進行拷貝,而只 是拷貝了這個對象的引用,否則會對該對象到最后一個可變類型的每一層 對象就行拷貝, 對每一層拷貝的對象都會開辟新的內存空間進行存儲

  • 可變類型的深拷貝(主要討論列表及子列表)

    In [77]: a = [1,2,[1,2,3]]
    
    In [78]: b = copy.deepcopy(a)
    # 外部列表內存地址改變
    In [79]: id(a)
    Out[79]: 2140687234824
    
    In [80]: id(b)
    Out[80]: 2140681187208
    
    # 子列表內存地址也改變
    In [81]: id(a[2])
    Out[81]: 2140681025608
    
    In [82]: id(b[2])
    Out[82]: 2140690147272
        
    # 改變子列表元素不會再影響deepcoy的子列表元素
    In [83]: a[2].append(3)
    In [84]: a
    Out[84]: [1, 2, [1, 2, 3, 3]]
        
    In [85]: b
    Out[85]: [1, 2, [1, 2, 3]]
    
    

    結論:

    可變類型進行深拷貝會對該對象到最后一個可變類型的每一層對象就行拷貝, 對每一層拷貝的對象都會開辟新的內存空間進行存儲。

總結

  • 淺拷貝使用copy.copy函數
  • 深拷貝使用copy.deepcopy函數
  • 不管是給對象進行深拷貝還是淺拷貝,只要拷貝成功就會開辟新的內存空間存儲拷貝的對象。
  • 淺拷貝和深拷貝的區別是:
    • 淺拷貝最多拷貝對象的一層,深拷貝可能拷貝對象的多層。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Swift1> Swift和OC的區別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,145評論 1 32
  • 1.設計模式是什么? 你知道哪些設計模式,并簡要敘述?設計模式是一種編碼經驗,就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,199評論 0 12
  • 一、GIL鎖 1.1、GIL面試題:描述Python GIL的概念, 以及它對python多線程的影響?編寫一個多...
    IIronMan閱讀 465評論 0 0
  • 1. 引用類型有哪些?非引用類型有哪些 引用類型:對象、數組、函數、正則;指的是那些保存在堆內存中的對象,變量中保...
    曾祥輝閱讀 222評論 0 0
  • 前世今生,一幕幕的在王飛的腦子里閃過,家破人亡,一切的一切究竟拜誰所賜。重生后他要讓那些欺負他的人付出代價。...
    小孩子吃飯閱讀 115評論 0 0