基础知识请查看之前博客:Python 对象:可变对象与不可变对象
赋值、浅拷贝和深拷贝的关键问题:
修改一个变量,会不会导致另外拷贝出来的对象的改变。
不可变对象:
import copy
a1 = 0
a2 = a1
a3 = copy.copy(a1)
a4 = copy.deepcopy(a1)
print(id(a1))
print(id(a2))
print(id(a3))
print(id(a4))
print('*' * 30)
b1 = 3.5
b2 = b1
b3 = copy.copy(b1)
b4 = copy.deepcopy(b1)
print(id(b1))
print(id(b2))
print(id(b3))
print(id(b4))
print('*' * 30)
c1 = 'abc'
c2 = c1
c3 = copy.copy(c1)
c4 = copy.deepcopy(c1)
print(id(c1))
print(id(c2))
print(id(c3))
print(id(c4))
print('*' * 30)
d1 = (1, 2, 3)
d2 = d1
d3 = copy.copy(d1)
d4 = copy.deepcopy(d1)
print(id(d1))
print(id(d2))
print(id(d3))
print(id(d4))
print('*' * 30)
结果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200411100904894.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
我们可以看到:对于可变对象来说,复制赋值、浅拷贝和深拷贝都是一样的,因为你修改的时候,由于是不可变对象,所以你修改的时候都会创建一个新的对象,然后要修改的变量重新指向新创建的对象。
例如:
import copy
a1 = 0
a2 = a1
a3 = copy.copy(a1)
a4 = copy.deepcopy(a1)
print(id(a1))
print(id(a2))
print(id(a3))
print(id(a4))
print('*' * 30)
a1 = 10
a2 = 20
print(id(a1))
print(id(a2))
print(id(a3))
print(id(a4))
print('*' * 30)
结果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200411101326365.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
我们修改 a1 和 a2 都是指向了新的对象,而 a3 和 a4 都还是原来的对象。
可变对象:
import copy
a1 = [[1, 2, 3], 'abc', 4]
a2 = a1
a3 = copy.copy(a1)
a4 = copy.deepcopy(a1)
print('Anchor')
print(a1)
print(id(a1))
print('=')
print(a2)
print(id(a2))
print('Copy')
print(a3)
print(id(a3))
print('Deepcopy')
print(a4)
print(id(a4))
print('*' * 30)
结果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200411102031556.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
我们发现:
- 复制赋值操作一样,并没有创建新的对象,而是创建了一个新的变量指向这个对象,也就是这个对象的一个新的别名。
- 浅拷贝和深拷贝都新建了一个新的对象,然后各自的变量名指向新的对象。
进一步,我们去看变量中每个元素的 id:
import copy
a1 = [[1, 2, 3], 'abc', 4]
a2 = a1
a3 = copy.copy(a1)
a4 = copy.deepcopy(a1)
print('Item in a1')
for i in a1:
print(id(i))
print('*' * 30)
print('Item in a2')
for i in a2:
print(id(i))
print('*' * 30)
print('Item in a3')
for i in a3:
print(id(i))
print('*' * 30)
print('Item in a4')
for i in a4:
print(id(i))
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200411102727409.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
我们发现:
- 复制赋值操作没什么意外,毕竟都是指向同一对象,进而里面每个元素的 id 也是一样的。
- 浅拷贝里面的元素不管是可变对象(例如第一个元素 [1, 2, 3])还是不可变对象(‘abc’,4)的 id 都是一样的,也就是说浅拷贝只是新建了一个对象,然后对象内部的元素和原对象的元素都是同样的。相互之间的修改可能会有影响。
- 深拷贝里面,对于可变对象(例如第一个元素 [1, 2, 3])id 是不同的,这个时候修改就完全独立了。这时候又发现不可变对象的 id 是一样的啊。这个啊,这个是为了节约内存,毕竟对不可变对象的修改都需要新建对象,然后重新指向啊。而可变对象的修改是在原有对象基础上的,所以深拷贝考虑这两者的差异,不可变对象 id 变了,可变对象的 id 不变。
总结:
- 复制赋值:没有新建任何对象,就是原对象的一个别名,对象 id 和对象元素的 id 都是一样的。
- 浅拷贝:Python 会分配一块新的内存用于创建新的拷贝对象,但拷贝对象中的元素依旧是原对象(被拷贝对象)中元素,即拷贝对象与原对象的 id 不同,但两者中的元素具有相同的 id。
- 深拷贝:Python会分配一块新的内存用于创建新的拷贝对象,拷贝对象中的元素是通过递归的方式将原对象中的元素一一复制过来的(可变元素除外,可变元素会创建一个新的对象,然后用原来对象的相应元素赋值之后指向新对象的元素),即对象与对象中的元素都是不同的 id,两者完全独立分离。
实例:
有了上面的理解,我们可以很容易的解释下面的行为:
Case1:
import copy
l1 = [[1, 2], (3, 4)]
l2 = l1
l3 = copy.copy(l1)
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l2')
for i in l2:
print(id(i))
print('Item in l3')
for i in l3:
print(id(i))
![![在这里插入图片描述](https://img-blog.csdnimg.cn/20200411104234853.png](https://img-blog.csdnimg.cn/20200411104633318.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
显然复制对象之中前两个 id 一致,最后一个 id 不同。浅拷贝出来的对象里面的元素指向也相同。
Case 2:
import copy
l1 = [[1, 2], (3, 4)]
l2 = l1
l3 = copy.copy(l1)
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l2')
for i in l2:
print(id(i))
print('Item in l3')
for i in l3:
print(id(i))
l1[0].append(3)
print(l1)
print(l2)
print(l3)
print('')
print("After action")
print('')
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l2')
for i in l2:
print(id(i))
print('Item in l3')
for i in l3:
print(id(i))
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200411104921787.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
显然对原对象中的可变对象元素修改会影响浅拷贝的对象,因为可变对象元素指向同一对象,并且由于是可变对象,所以修改的时候在原有对象上面修改 (l1 的第一个元素的 id 没有变),因此会有影响。
浅拷贝对象的可变对象元素的修改,相互之间影响。
Case 3:
import copy
l1 = [[1, 2], (3, 4)]
l2 = l1
l3 = copy.copy(l1)
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l2')
for i in l2:
print(id(i))
print('Item in l3')
for i in l3:
print(id(i))
l3[0].append(4)
print(l1)
print(l2)
print(l3)
print('')
print("After action")
print('')
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l2')
for i in l2:
print(id(i))
print('Item in l3')
for i in l3:
print(id(i))
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200411105116284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
修改复制出来的对象中的可变对象元素行为也是一样的:会相互影响!
Case 4:
import copy
l1 = [[1, 2], (3, 4)]
l2 = l1
l3 = copy.copy(l1)
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l2')
for i in l2:
print(id(i))
print('Item in l3')
for i in l3:
print(id(i))
l1[1] += (5, 6)
print(l1)
print(l2)
print(l3)
print('')
print("After action")
print('')
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l2')
for i in l2:
print(id(i))
print('Item in l3')
for i in l3:
print(id(i))
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020041110525274.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
修改原对象中不可变对象元素,由于不可变对象不能直接修改,所以需要新建一个元素对象,然后重新指向。我们可以看到 l2 和 l1 一样(指向同一对象,同一对象的别名),都响应了变化,并且第二个元素的 id 也发现了变化。但是 l3 没有任何变化,这是因为它还是指向修改之前的对象。
浅拷贝对象的不可变对象元素的修改,相互不影响。
Case 5:
import copy
l1 = [[1, 2], (3, 4)]
l4 = copy.deepcopy(l1)
print(id(l1))
print(id(l4))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l4')
for i in l4:
print(id(i))
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200411110358598.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
显然,为了避免浅拷贝中可变对象相互影响的问题,深拷贝为要复制对象的可变对象元素新建了一个对象,然后赋值,最后深拷贝对象的相应元素指向它。则也就是 l1 和 l4 第一个元素 id 不同的原因,对于可变对象来说,为了节约内存,还是指向同一对象,毕竟修改的时候都会新建对象。
Case 6:
import copy
l1 = [[1, 2], (3, 4)]
l4 = copy.deepcopy(l1)
print(id(l1))
print(id(l4))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l4')
for i in l4:
print(id(i))
l1[0] += [3]
l1[1] += (5, 6)
print(l1)
print(l4)
print('')
print("After action")
print('')
print(id(l1))
print(id(l4))
print('*'*30)
print('Item in l1')
for i in l1:
print(id(i))
print('Item in l4')
for i in l4:
print(id(i))
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020041111045993.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4OTU3NQ==,size_16,color_FFFFFF,t_70)
相应的,可变对象元素的修改还是在原来对象上的,所以修改前后 l1 的第一个元素 id 不变,但是由于深拷贝的时候为可变对象元素时新建了一个对象的,所以不会影响。不可变对象元素的修改会新建一个对象,所以修改前后 l1 的第二个元素 id 发生了变化,即使 l4 的第二个元素和原来的 id 一样,也是不同的一个值。
深拷贝对象的可变对象元素的修改,相互之间也不影响了。
其他:
浅拷贝除了 copy.copy() 之外还有工厂模式(例如列表的 list(l1))和切片操作(l3 = l1[::])产生。
总结:
理解关键:
- 可变对象和不可变对象的修改行为差异:可变对象修改在原有对象上,不可变对象的修改是新建一个对象,赋予新值,然后重新指向。
- 浅拷贝只是新建了外层对象,内部元素还是和原对象的内部元素指向一样,因此浅拷贝的可变对象元素的修改会相互影响。
- 深拷贝不仅新建了外层对象,还为内部元素中的可变对象元素新建了对象,因此深拷贝的可变对象元素的修改相互不会影响。(不可变对象由于修改一定会新建对象,因此深拷贝中的内部元素的不可变对象也是直接拷贝过来的(id 相同))。