深入理解赋值,浅拷贝,深拷贝

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_29027865/article/details/86751509

前言

在说明之前,先丢一个问题:

import copy
t = ['a','b']
a = [1,2,3,t]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

a.append(4)
b = ?
c = ?
d = ?
t.append('c')
b = ?
c = ?
d = ?

以上三个值分别代表:直接赋值浅拷贝深拷贝
如果你对上面问题的答案还不确定,接下来我们就来逐个介绍它们是什么以及其中的区别。
确认的话可以直接跳转四

一. 直接赋值与拷贝的区别

a = [11,22,33]
b = a
id(a)
-->4504216328
id(b)
-->4504216328

可以看到通过直接赋值的b和a的id相同,即变量b和a指向同一块内存地址
我们将其与拷贝对比,如上的情况:

import  copy
a = [11,22,33]
c = copy.copy(a)
d = copy.deepcopy(a)
print(id(a))
-->4504215816
print(id(c))
-->4504352520
print(id(d))
-->4503501704

明显看到,copy和deepcopy均是指向了一个新的内存空间;

我们对a改变,观察直接赋值和copy模块的区别:

a.append(44)
print(a)
-->[11, 22, 33, 44]
print(b)
-->[11, 22, 33, 44]
print(c)
-->[11, 22, 33]
print(d)
-->[11, 22, 33]

由此可以知道,直接赋值的拷贝和原变量共享一个对象,其直接把变量的地址复制过去,得到原变量的引用,而没有去指向新的内存空间。
而copy模块则是指向了新的内存空间。

可以描述如下:
在这里插入图片描述

二. 浅拷贝copy与深拷贝deepcopy的区别

在上面,我们已经知道了copy模块是开辟一个新的内存空间,那么浅拷贝copy和深拷贝deepcopy有什么区别呢?
当要拷贝的对象中仍然嵌套着一个对象时,copy的内容是指向嵌套子对象的引用?
还是又开辟了新的内存空间,复制了嵌套子对象的值?
我们通过这个例子来验证一下想法:

import copy
a = [1,2,3]
b = [4,5,6]
c = [a,b]
d = copy.copy(c)
e = copy.deepcopy(c)
a.append(4)
print(c)
print(d)
print(e)

如果是拷贝指向嵌套子对象的引用,那么我们改变嵌套子对象的值,则拷贝的也会改变;
如果是拷贝的是嵌套子对象的值,那么我们改变嵌套子对象的值,则拷贝的则不会改变。
得到的结果如下:

[[1, 2, 3, 4], [4, 5, 6]]
[[1, 2, 3, 4], [4, 5, 6]]
[[1, 2, 3], [4, 5, 6]]

可以看到,copy.copy的值随着嵌套子对象a的值改变而改变,可以描述如下:
在这里插入图片描述
如果拷贝的可变对象中仍有嵌套的子对象,那么copy.copy就只复制一层,子对象使用之前的引用,而copy.deepcopy则会递归的拷贝对象下子对象的内容;

三. 扩展-不可变对象的copy

还是先由内存空间id开始:

a = [1,2,3]
b = [4,5,6]
c = (a,b)
d = c
e = copy.copy(c)
f = copy.deepcopy(c)

按照前面的实验,我们先猜想下print出id的结果,应该是d和c相同,因为d指向的是c的内存空间,e,f均为copy,其应该指向不同的内存空间,但是结果是:

print(id(c))
print(id(e))
print(id(f))
4502541960
4502541960
4502714696

???为什么copy.copy也是指向了和c相同的内存空间?
原因是:元组为不可变类型,copy.copy能够判断是否为可变类型:如果是可变类型,使用copy功能时,只完成一层拷贝;如果时不可变类型时,那使用copy类型时,就只去引用,和赋值的效果一样。
而当子对象改变时,其结果仍然是和上述相同:

print(c)
print(d)
print(e)
print(f)
([1, 2, 3, 4], [4, 5, 6])
([1, 2, 3, 4], [4, 5, 6])
([1, 2, 3, 4], [4, 5, 6])
([1, 2, 3], [4, 5, 6])

四. 前言问题的答案

对于这个问题,我们先看下原拷贝对象的结构,其由不变的值1,2,3和嵌套子对象(列表)组成,那么对于直接赋值,copy.copy,copy.deepcopy可以描述如下:
在这里插入图片描述
根据上面的描述,那么:
(1) 当a的固定值改变时
b引用a的地址,b的状态随着a而改变,b = [1,2,3,[‘a’,‘b’],4]
c是copy.copy,因为改变的是常量,故c的值会不变,c = [1,2,3,[‘a’,‘b’]]
d是copy.deepcopy,所有的值都是自己独有的,故d的值也不会变,
d = [1,2,3,[‘a’,‘b’]]
(2) 当a的嵌套子对象列表改变时
b引用a的地址,b的状态随着a而改变,b = [1,2,3,[‘a’,‘b’,‘c’]]
c是copy.copy,其只copy一层,对于嵌套子对象用的仍是原来的引用,故c的值会随着a而改变,
c = [1,2,3,[‘a’,‘b’,‘c’]]
d是copy.deepcopy,所有的值都是自己独有的,故d的值仍然不会变,
d = [1,2,3,[‘a’,‘b’]]

程序输出结果和推理一致:

当a的固定值改变时
[1, 2, 3, ['a', 'b'], 4]
[1, 2, 3, ['a', 'b']]
[1, 2, 3, ['a', 'b']]
当a的嵌套子对象列表改变时
[1, 2, 3, ['a', 'b', 'c']]
[1, 2, 3, ['a', 'b', 'c']]
[1, 2, 3, ['a', 'b']]

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_29027865/article/details/86751509
今日推荐