由Python的淺拷貝(shallow copy)和深拷貝(deep copy)引發的思考


首先查看拷貝模塊(copy)發現:

>>> help(copy)
Help on module copy:
NAME
    copy - Generic (shallow and deep) copying operations.
DESCRIPTION
    Interface summary:   
            import copy   
            x = copy.copy(y)        # make a shallow copy of y
            x = copy.deepcopy(y)    # make a deep copy of y   
    For module specific errors, copy.Error is raised.   
    The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or
    class instances).
    
    - A shallow copy constructs a new compound object and then (to the extent possible) inserts *the same objects* into it that the
      original contains.
    
    - A deep copy constructs a new compound object and then, recursively,  inserts *copies* into it of the objects found in the original.

    ...(here omitted 10000words)

由以上的信息可知:

    1、相同點:都拷貝出了一個新的復合對象;

    2、不同點:淺拷貝—— 在拷貝出的新的對象上插入(引用)源list對象的一切;

                    深拷貝—— 遞歸地拷貝源list對象中的一切。(徹頭徹尾的另立門戶)

 

現在出現了一個新的問題—— 拷貝

在計算機中拷貝一份數據或者拷貝一個變量,意味着系統需分配新的內存用於對拷貝數據的存放。

我們先來討論一下變量的賦值(變量的數據結構中的內存地址域的拷貝)過程。

首先看一下變量的賦值過程:

 1 Python 2.6.6 (r266:84292, Aug 18 2016, 15:13:37) 
 2 [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux2
 3 Type "help", "copyright", "credits" or "license" for more information.
 4 >>> a = 3
 5 >>> b = a
 6 >>> id(a)
 7 7488264
 8 >>> id(b)
 9 7488264
10 >>> a = 4
11 >>> id(a)
12 7488240
13 >>> id(b)               # 咦,b沒有隨a發生改變
14 7488264
15 >>> b
3

要解釋這個,必須要了解變量的數據結構。

當向系統申請創建一個變量時,系統先分配一塊內存空間,該內存空間用於存儲該變量。

變量的數據結構包括2部分:第一部分用於存儲變量的名稱和變量的數據類型的長度,第二部分用於存儲內存地址(即索引)。

當變量未初始化時,第二部分數據為垃圾值;一旦初始化,該部分的值即為初始化值的內存地址。

例如:以上 a = 3, 其過程如下:

首先系統為常量3(int型)分配一塊內存大小為4byte的空間存放常量3;然后將常量3的內存地址存儲於變量a的第二部分。這樣就完成了變量a的賦值過程。

b = a時,同樣系統先分配一塊內存空間存放變量b, 之后系統將a中的第二部分數據拷貝到b中的第二部分。

而id()的返回值正是變量的第二部分數據(內存地址)。

所以當執行a時,是根據第二部分的數據(內存地址)獲取該內存的值。

當a = 4 時,變量a第二部分的數據即為常量4的存儲地址,因此id(a)發生改變,而id(b)保持不變。

如下圖:

 

回到淺拷貝和深拷貝的議題:

淺拷貝—— 在拷貝出的新的對象上插入(引用)源list對象的一切;                   

深拷貝—— 遞歸地拷貝源list對象中的一切。(徹頭徹尾的另立門戶)。

淺拷貝的實例:

 1 #!/usr/bin/python                  # Python2
 2 #
 3 import copy
 4 
 5 will = ["Will", 28, ["Python", "C#", "JavaScript"]]
 6 wilber = copy.copy(will)
 7 
 8 print id(will)                     # 140337318319672
 9 print will                         # ['Will', 28, ['Python', 'C#', 'JavaScript']]
10 print [id(ele) for ele in will]    # [140337318374208, 13394096, 140337318282160]
11 print '============================'
12 print id(will[2])                  # 140337318282160
13 print id(will[2][0])               # 140337318677600
14 print id(wilber[2][0])             # 140337318677600
15 print id(wilber)                   # 140337318386216
16 print wilber                       # ['Will', 28, ['Python', 'C#', 'JavaScript']]
17 print [id(ele) for ele in wilber]  # [140337318374208, 13394096, 140337318282160]
18   
19 will[0] = "Wilber"
20 will[2].append("CSS")
21 print id(will)                     # 140337318319672
22 print will                         # ['Wilber', 28, ['Python', 'C#', 'JavaScript', 'CSS']]
23 print [id(ele) for ele in will]    # [140337318374448, 13394096, 140337318282160]
24 print id(wilber)                   # 140337318386216
25 print wilber                       # ['Will', 28, ['Python', 'C#', 'JavaScript', 'CSS']]
26 print [id(ele) for ele in wilber]  # [140337318374208, 13394096, 140337318282160]

 淺拷貝只是生成一個新的對象,數據結構以及索引關系未變。

淺拷貝時,列表will與wilber由系統分配不同的地址,系統將列表will的第一層進行拷貝即:will[0], will[1], will[2]拷貝,故wilber[0]與will[0],wilber[1]與will[1],wilber[2]與will[2],指向相同的內存地址。

 如下圖所示:

 

深拷貝實例:

 1 #!/usr/bin/python
 2 #
 3 import copy
 4  
 5 will = ["Will", 28, ["Python", "C#", "JavaScript"]]
 6 wilber = copy.deepcopy(will)
 7 
 8 print id(will)                    # 139899797283040    
 9 print will                        # ['Will', 28, ['Python', 'C#', 'JavaScript']]
10 print [id(ele) for ele in will]   # [139899797338992, 11432112, 139899797246896]
11 print '============='
12 print id(will[2])                 # 139899797246896
13 print id(wilber[2])               # 139899797351024
14 print id(will[2][0])              # 139899797642336
15 print id(wilber[2][0])            # 139899797642336
16 print id(wilber[2][1])            # 139899797339088
17 print id(wilber)                  # 139899797349296
18 print wilber                      # ['Will', 28, ['Python', 'C#', 'JavaScript']]
19 print [id(ele) for ele in wilber] # [139899797338992, 11432112, 139899797351024]
20  
21 will[0] = "Wilber"
22 will[2].append("CSS")
23 print id(will)                    # 139899797283040
24 print will                        # ['Wilber', 28, ['Python', 'C#', 'JavaScript', 'CSS']]
25 print [id(ele) for ele in will]   # [139899797339280, 11432112, 139899797246896]
26 print id(wilber)                  # 139899797349296
27 print wilber                      # ['Will', 28, ['Python', 'C#', 'JavaScript']]
28 print [id(ele) for ele in wilber] # [139899797338992, 11432112, 139899797351024]

深拷貝會遞歸(逐層)拷貝list的數據結構。

深拷貝時,系統將列表will逐層進行拷貝即:列表will與wilbe,will[2]與wilber[2]由系統分配不同的地址,will[0], will[1], will[2],will[2][0], will[2][1], will[2][2]拷貝;

故wilber[0]與will[0],wilber[1]與will[1], will[2][0]與wilber[2][0], will[2][1]與wilber[2][0], will[2][2]與wilber[2][2],指向相同的內存地址。

 

附注-list之間的賦值代碼:

 1 #!/usr/bin/python
 2 #
 3 will = ["Will", 28, ["Python", "C#", "JavaScript"]]
 4 wilber = will
 5 print id(will)
 6 print will
 7 print [id(ele) for ele in will]
 8 print id(wilber)
 9 print wilber
10 print [id(ele) for ele in wilber]
11  
12 will[0] = "Wilber"
13 will[2].append("CSS")
14 print id(will)
15 print will
16 print [id(ele) for ele in will]                # 發現操作的是同一對象
17 print id(wilber)
18 print wilber
19 print [id(ele) for ele in wilber]
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM