欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python 深拷贝、浅拷贝

程序员文章站 2022-03-10 22:16:02
...

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

深拷贝、浅拷贝

1. 浅拷贝

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

Python 深拷贝、浅拷贝
浅拷贝示意图

使用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]:   

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

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

Python 深拷贝、浅拷贝

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]:     

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

Python 深拷贝、浅拷贝

字典的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]:  
Python 深拷贝、浅拷贝

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]:     
Python 深拷贝、浅拷贝

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],)]
Python 深拷贝、浅拷贝

来看看完成使用可变的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]:   

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


Python 深拷贝、浅拷贝

copy.deepcopy

Python 深拷贝、浅拷贝

Python 深拷贝、浅拷贝

Python 深拷贝、浅拷贝
Python 深拷贝、浅拷贝

关注微信公众号,回复【资料】、Python、PHP、JAVA、web,则可获得Python、PHP、JAVA、前端等视频资料。