Python 深拷贝、浅拷贝

时间:2022-06-22
本文章向大家介绍Python 深拷贝、浅拷贝,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

仅供学习参考,转载请注明出处

深拷贝、浅拷贝

1. 浅拷贝

浅拷贝是对于一个对象的顶层拷贝 通俗的理解是:拷贝了引用,并没有拷贝内容

浅拷贝示意图

使用ipython3编写几个示例来看看:

In [1]: a = [1,2,3,4]                                                                     

In [2]: b = a                                                                             

In [3]: a                                                                                 
Out[3]: [1, 2, 3, 4]

In [4]: b                                                                                 
Out[4]: [1, 2, 3, 4]

In [5]: id(a) # 查看变量a的内存地址                                                       
Out[5]: 140490275823112

In [6]: id(b) # 查看变量b的内存地址                                                       
Out[6]: 140490275823112

In [7]: import copy                                                                       

In [8]:                                                                                   

In [8]: c = copy.copy(a) # 使用copy拷贝 a                                               

In [9]: c                                                                                 
Out[9]: [1, 2, 3, 4]

In [10]: id(c) # 查看变量c的内存地址                                                      
Out[10]: 140490271207112

In [11]:                  

从上面的示例来看,b = ac = copy.copy(a) 这两种方式下,b 与 a 的内存地址都是 140490275823112 ,但是 c 的内存地址却是 140490271207112 。c 已经指向了另一个内存地址了。 说明: b = a 符合浅拷贝的规则。

思考:既然浅拷贝都是指向同一个内存地址,那么是不是修改一个变量的话,是不是另一个变量指向的值都会一起修改呢?

# 首先查看一下上一个步骤之后, a b c 三个变量的值
In [11]: c                                                                                
Out[11]: [1, 2, 3, 4]

In [12]: a                                                                                
Out[12]: [1, 2, 3, 4]

In [13]: b                                                                                
Out[13]: [1, 2, 3, 4]

# 因为 a b 两个变量都是指向同一个内存地址,那么给 b 的list增加一个 5,查看一下变量的修改
In [14]: b.append(5)                                                                      

In [15]: b                                                                                
Out[15]: [1, 2, 3, 4, 5]

In [16]: a                                                                                
Out[16]: [1, 2, 3, 4, 5]

In [17]: c                                                                                
Out[17]: [1, 2, 3, 4]   

# 可以从上面三个变量看出,a与b变量是同时修改了,而c因为不同内存地址,所以并没有修改。

# 那么修改变量c,增加一个数字6到list中,当然是不会影响变量a与b的,实践看看。
In [18]: c.append(6)                                                                      

In [19]: c                                                                                
Out[19]: [1, 2, 3, 4, 6]

In [20]: a                                                                                
Out[20]: [1, 2, 3, 4, 5]

In [21]: b                                                                                
Out[21]: [1, 2, 3, 4, 5]

In [22]:                           

注意: 其实上面的理解对于浅拷贝是有一定的偏差的,虽然 b = a 的确属于浅拷贝的一种,但是浅拷贝 c = copy.copy(a) 也是属于浅拷贝的另一种,那么为什么内存不一样呢?

其实浅拷贝只是拷贝最上面的那一层数据,其实也是会生成一个新的变量,此时内存就会不一样。下面再来一个示例演示一下:

In [22]: d = [a,b]      # 首先设置变量 d 加入 a 与 b                                                                  

In [23]: e = copy.copy(d)         # 使用 e 浅拷贝 d,此时就会生成一个新的内存变量 e                                                        

In [24]: id(d)     # 查看d的变量内存                                                
Out[24]: 140490271478024

In [25]: id(e)   # 查看e的变量内存,果然是跟 d 不同的。但是e 浅拷贝过来的 a 与 b 的地址呢?                                               
Out[25]: 140490295815880

In [26]: id(d[0])     # 首先查看 变量 a 在 d 的list地址                                                               
Out[26]: 140490275823112

In [27]: id(e[0])     # 查看变量 a 在 e 的list内存地址,居然是一样的。                                                              
Out[27]: 140490275823112

In [28]: id(d[1])                                                                         
Out[28]: 140490275823112

In [29]: id(e[1])                                                                         
Out[29]: 140490275823112

In [30]:  

从上面的结果来看,c 与 d 的变量内存地址不一样,但是 c 与 d 里面的 a 和 b 的内存地址是一样的。 那么是不是就是如果修改 a ,那么 c 与 d 会同时修改呢?

In [30]: a.append(7)                                                                      

In [31]: a                                                                                
Out[31]: [1, 2, 3, 4, 5, 7]

In [32]: b                                                                                
Out[32]: [1, 2, 3, 4, 5, 7]

In [33]: e[0]                                                                             
Out[33]: [1, 2, 3, 4, 5, 7]

In [34]: d[0]                                                                             
Out[34]: [1, 2, 3, 4, 5, 7]

In [35]:   

答案是会同时修改的,因为内存地址都一致。

这里提供一个理解示意图:

2. 深拷贝

深拷贝是对于一个对象所有层次的拷贝(递归)

In [35]: a = [11,22]                                                                      

In [36]: b = copy.deepcopy(a) # 对a指向的列表进行深copy                                   

In [37]: a                                                                                
Out[37]: [11, 22]

In [38]: b                                                                                
Out[38]: [11, 22]

In [39]: id(a)       # 查看 a 的内存地址                                                                     
Out[39]: 140490295123912

In [40]: id(b)       # 查看 b 的内存地址,可以看出与变量 a 是不一致的。                                                                     
Out[40]: 140490295270088

In [43]: a.append(33)        # 那么 a 增加一个变量 33 肯定不会影响 b,执行看看                                                             

In [44]: a                                                                                
Out[44]: [11, 22, 33]

In [45]: b                                                                                
Out[45]: [11, 22]

In [46]:   

但是从这个例子,看不出深拷贝特殊之处。

进一步理解深拷贝

从前面浅拷贝的例子中,我们来看看使用深拷贝有什么变化。

In [46]: a = [1,2,3,4]                                                                    

In [47]: b = a                                                                            

In [48]: d = [a,b]                                                                        

In [49]: e = copy.deepcopy(d) # 使用 e 深拷贝 d ,此时会深度递归 d 里面的所有变量          

In [50]: id(d)      # 查看变量 d 的内存                                                                      
Out[50]: 140490296753416

In [51]: id(e)      # 查看变量 e 的内存,可以看出 e 与 d 的内存地址是不同的。                                                                      
Out[51]: 140490295210696

# 前面案例中,d[0] 的内存地址 是 与 c[0] 的内存地址是一样的,这里来看深拷贝是不一样
In [52]: id(d[0])                                                                     
Out[52]: 140490271451720

In [53]: id(e[0])                                                                         
Out[53]: 140490294520008

In [54]: id(e[1])                                                                         
Out[54]: 140490294520008

In [55]: id(d[1])                                                                         
Out[55]: 140490271451720
# 从上面打印来看,深拷贝后,e 与 d 的所有内存变量都是不同的了。

In [56]: d                                                                                
Out[56]: [[1, 2, 3, 4], [1, 2, 3, 4]]

In [57]: e                                                                                
Out[57]: [[1, 2, 3, 4], [1, 2, 3, 4]]

In [58]: a.append(5)                                                                      

In [59]: d                                                                                
Out[59]: [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]

In [60]: e                                                                                
Out[60]: [[1, 2, 3, 4], [1, 2, 3, 4]]

In [61]:     

3. 拷贝的其他方式

分片表达式d = c[:]可以赋值一个序列

In [1]: a = [11,22]                                                        

In [2]: b = [33,44]                                                        

In [3]: c = [a,b]                                                          

In [4]: d = c[:] # 分片表达式                                              

In [5]: c                                                                  
Out[5]: [[11, 22], [33, 44]]

In [6]: d                                                                  
Out[6]: [[11, 22], [33, 44]]

# 查看使用分片表达式传值后的变量内存,可以看出 c 与 d 是不同的。
In [7]: id(c)                                                              
Out[7]: 140089352809416

In [8]: id(d)                                                              
Out[8]: 140089352792904

# 那么看看 c[0] 与 d[0] 的内存变量,从结果来看是相同的。
In [9]: id(c[0])                                                           
Out[9]: 140089352742024

In [10]: id(d[0])                                                          
Out[10]: 140089352742024

# 那么既然内存地址都相同,那么给a增加一个33的变量,查看是否同时修改值
In [11]: a                                                                 
Out[11]: [11, 22]

In [12]: a.append(33)                                                      

In [13]: a                                                                 
Out[13]: [11, 22, 33]

In [14]: c                                                                 
Out[14]: [[11, 22, 33], [33, 44]]

In [15]: d                                                                 
Out[15]: [[11, 22, 33], [33, 44]]

In [16]:     

从上面的结果来看,分片表达式就是一种浅拷贝。

字典的copy方法可以拷贝一个字典

In [16]: import copy                                                                   

# 创建一个字典
In [17]: d = dict(name="zhangsan",age=27)                                              

# 将字典d 拷贝到 co
In [18]: co = d.copy()                                                                 

# 查看一下 d 与 co 的值
In [19]: d                                                                             
Out[19]: {'name': 'zhangsan', 'age': 27}

In [20]: co                                                                            
Out[20]: {'name': 'zhangsan', 'age': 27}

# 查看一下 d 与 co的内存值,可以看出是不同的。
In [21]: id(d)                                                                         
Out[21]: 140089352402120

In [22]: id(c)                                                                         
Out[22]: 140089352809416

In [23]:                                                                               

# 那么直接给 d 设置新的字典内容
In [23]: d = dict(name="zhangsan",age=27,children_ages = [11,22])                      

In [24]: d                                                                             
Out[24]: {'name': 'zhangsan', 'age': 27, 'children_ages': [11, 22]}

In [26]: co                                                                            
Out[26]: {'name': 'zhangsan', 'age': 27}

# 重新将 d 拷贝到 co
In [27]: co = d.copy()                                                                 

In [28]: co                                                                            
Out[28]: {'name': 'zhangsan', 'age': 27, 'children_ages': [11, 22]}

# 给字典里面的 children_ages 增加 list 数字 9
In [29]: d["children_ages"].append(9)                                                  

# 可以看到 co 也跟着一起变化了,说明 children_ages 的内存地址是一致的。
In [30]: d                                                                             
Out[30]: {'name': 'zhangsan', 'age': 27, 'children_ages': [11, 22, 9]}

In [31]: co                                                                            
Out[31]: {'name': 'zhangsan', 'age': 27, 'children_ages': [11, 22, 9]}

In [32]:    

In [32]: id(d["children_ages"])                                                        
Out[32]: 140089267051336

In [33]: id(co["children_ages"])                                                       
Out[33]: 140089267051336

In [34]:  

4. 注意点

浅拷贝对不可变类型和可变类型的copy不同

  • copy.copy对于可变类型,会进行浅拷贝
  • copy.copy对于不可变类型,不会拷贝,仅仅是指向
# 拷贝list可变类型
In [34]: a = [11,22,33]                                                                

In [35]: b = copy.copy(a)                                                              

In [36]: id(a)                                                                         
Out[36]: 140089256561608

In [37]: id(b)                                                                         
Out[37]: 140089352086664

In [38]: a.append(44)                                                                  

In [39]: a                                                                             
Out[39]: [11, 22, 33, 44]

In [40]: b                                                                             
Out[40]: [11, 22, 33]

In [41]:  

# 使用元祖再来演示一下,可以看出拷贝的两个变量内存地址一致。
In [41]: a = (11,22,33)                                                                

In [42]: b = copy.copy(a)                                                              

In [43]: id(a)                                                                         
Out[43]: 140089283270624

In [44]: id(b)                                                                         
Out[44]: 140089283270624

In [45]:     

copy.copy和copy.deepcopy的区别

copy.copy

In [45]: a = [11,22]                                                                   

# 使用元组来括起来 a ,那么后续的内存地址就不会变
In [46]: b = (a, )                                                                     

In [47]: b                                                                             
Out[47]: ([11, 22],)

In [48]: c = [b,]                                                                      

In [49]: c                                                                             
Out[49]: [([11, 22],)]

# 使用 d 浅拷贝 c,那么 c 里面的元组内存地址会不会变化呢?
In [50]: d = copy.copy(c)                                                              

In [51]: d                                                                             
Out[51]: [([11, 22],)]

# 首先查看一下变量 c 与 d 的内存地址,发现是不同的,正常。
In [52]: id(c)                                                                         
Out[52]: 140089267047816

In [53]: id(d)                                                                         
Out[53]: 140089352213384

# 查看 c 与 d 的元组内存地址,发现是一样的,那么就是最初的变量 a 的地址
In [57]: id(c[0])                                                                      
Out[57]: 140089352719216

In [58]: id(d[0])                                                                      
Out[58]: 140089352719216

# 给变量 a 增加一个 33的数字,那么 c 与 d 就会同时一起增加,如下:
In [54]: a.append(33)                                                                  

In [55]: c                                                                             
Out[55]: [([11, 22, 33],)]

In [56]: d                                                                             
Out[56]: [([11, 22, 33],)]

来看看完成使用可变的list示例

In [60]: a = [11,22]                                                                   

In [61]: b = [a]                                                                       

In [62]: b                                                                             
Out[62]: [[11, 22]]

In [63]: c = [b]                                                                       

In [64]: c                                                                             
Out[64]: [[[11, 22]]]

In [65]: d = copy.copy(c)                                                              

In [66]: d                                                                             
Out[66]: [[[11, 22]]]

In [67]: id(d)                                                                         
Out[67]: 140089282147016

In [68]: id(c)                                                                         
Out[68]: 140089352347848

In [69]: id(d[0])                                                                      
Out[69]: 140089356847432

In [70]: id(c[0])                                                                      
Out[70]: 140089356847432

In [71]: id(d[0][0])                                                                   
Out[71]: 140089282502536

In [72]: id(c[0][0])                                                                   
Out[72]: 140089282502536

In [73]: c                                                                             
Out[73]: [[[11, 22]]]

In [74]: d                                                                             
Out[74]: [[[11, 22]]]

In [75]: a                                                                             
Out[75]: [11, 22]

In [76]: a.append(33)                                                                  

In [77]: c                                                                             
Out[77]: [[[11, 22, 33]]]

In [78]: d                                                                             
Out[78]: [[[11, 22, 33]]]

In [79]: a                                                                             
Out[79]: [11, 22, 33]

In [80]:   

从上面的操作来看,只要是浅拷贝可变的变量,那么内部的数据都是会指向同一个内存地址的。

copy.deepcopy