python之深浅copy

python之深浅copy

引入

1.为什么要使用深浅拷贝

  • 涉及到容器类型的修改操作时,想要保留原来的数据或修改后的数据,这是就需要使用到深浅拷贝来进行操作了

  • 以下使用列表list1 = [ "str", 123, [111,222]]拷贝来进行实验

一.赋值运算

1.赋值运算原理

  • 赋值操作是指源列表与新列表指向的是同一个内存地址,无论是通过新变量名还是原变量名改变变量值内的元素,二者所对应的变量值都会改变(因为是同一个)。
  • list2 = list1

赋值操作

2.示例

?创建一个列表,包含字符串,整形和列表(可变),并将其赋值给另一个变量名
list1 = ["str", 123, [111,222]]
list2 = list1

?查看两个列表的"id",可以发现"id"不变,是同一个
print(id(list1))  #2248006456392
print(id(list2))  #2248006456392
print(id(list1[0]),id(list1[1]),id(list1[2]))
# 2248006252720 140725163491392 2248006455880
print(id(list2[0]),id(list2[1]),id(list2[2]))
# 2248006252720 140725163491392 2248006455880

?当改变"list1"内的第一或者第二个元素(不可变元素)
list1[0] = "aaa"
print(list1[0],id(list1[0]))
# aaa 2248007720496
print(list2[0],id(list2[0]))
# aaa 2248007720496
?发现"list1"改变的值的内存地址已经变化了,说明是产生的新值,与原来的"str"取消了对应
?而"list2"中的值和"id"也跟着改变

?当改变子列表里面的值时
list1[2][0] = 333
print(list1[2],id(list1[2]))
# [333, 222] 2248006455880
print(list2[2],id(list2[2]))
# [333, 222] 2248006455880
?我们发现两个列表的值都发生了改变,但子列表的"id"还都是不变的

3.总结

  • 赋值操作列表与新列表都是指向同一内存地址,所以他们都完全一样。
  • 2个列表中,只要有一个人的列表中的索引所对应的值的内存地址改变,则都改变
  • 也就是把源列表容器的内存地址完完整整的多绑定一份交给新列表

二、拷贝

拷贝是音译的词,是从copy这个英文单词音译过来的,copy其实就是复制一份。

在Python中,列表的内存地址存放的是元素的索引与元素内存地址的对应关系。

三.浅拷贝

什么是浅拷贝?

浅拷贝指仅拷贝对象的第一层,是在内存中重新开辟了一个空间存放索引与元素内存地址的对应关系。

对于原列表内的不可变数据类型,浅拷贝指向的内存地址和原对象指向的内存地址是同一个。当原对象第一层的元素改变,指向一个新的内存地址,而浅拷贝的内存地址指向的还是原内存地址。当原对象的深层元素改变,指向一个新的内存地址,浅拷贝的内存地址指向也会跟着变。

对于列表内的可变数据类型,浅拷贝指向的内存地址和原对象指向的内存地址是也同一个。但对**子列表内的元素,并没有指向列表内元素的内存地址。当子列表内的元素改变,子列表的内存地址并没有变,子列表内的元素内存地址改变。**浅拷贝指向子列表的内存地址不变,原子列表内存元素地址改变,浅拷贝指向的子列表元素内存地址也一起改变。

结论:当列表内第一层不可变数据类型的元素改变,浅拷贝的元素不会改变(还是指向原内存地址)。当列表内可变数据类型的子元素改变,浅拷贝的可变数据类型内的子元素也会跟着一起改变(仅指向可变数据类型本身)。

img

image-20201124091611162

1.浅拷贝原理

  • 把源列表第一层的内存地址不加区分(不区分是可变还是不可变类型)的完全copy给一份新列表
  • 对源列表copy之后, 产生的新列表的内存地址发生了改变, 它们不再是同一个列表
  • 但是新列表与源列表中的可变和不可变类型的值在修改之前都是指向同一个值
  • list2 = list1.copy()

浅拷贝

创建浅拷贝的几种方式:
  • 列表内置方法
l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = copy.copy((l1))
  • 完全切片
l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = l1[:]
  • copy模块
import copy
l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = copy.copy(l1)

2.示例

?创建一个列表,包含字符串,整形和列表(可变),并将其浅拷贝
list1 = ["str", 123, [111,222]]
list2 = list1.copy()

?查看两个列表的"id",可见是产生了新的一个内存地址
print(id(list1))  #2243511014472
print(id(list2))  #2243541368584
print(id(list1[0]),id(list1[1]),id(list1[2]))
# 2243510810800 140725163491392 2243511013960
print(id(list2[0]),id(list2[1]),id(list2[2]))
# 2243510810800 140725163491392 2243511013960
?复制过来的元素内存地址都是同一个,说明复制的只是变量名与内存地址的对应关系,指向的是同一个内存地址

?当改变"list1"内的第一或者第二个元素(不可变元素)
list1[0] = "aaa"
print(list1[0],id(list1[0]))
# aaa 2240322768432
print(list2[0],id(list2[0]))
# str 2243510810800
?发现"list1"改变的值的内存地址已经变化了,说明是产生的新值,与原来的"str"取消了对应
?而"list2"中的值和"id"都没变,说明还是原来的对应关系

?当改变子列表里面的值时
list1[2][0] = 333
print(list1[2],id(list1[2]))
# [333, 222] 2243511013960
print(list2[2],id(list2[2]))
# [333, 222] 2243511013960
?我们发现两个列表的值都发生了改变,但子列表的"id"还都是不变的

3.总结

  • 对源列表中不可变类型的值进行修改以后,对于不可变类型的值,都是产生新值,让源列表的索引指向新的内存地址,并不会影响新列表
  • 对源列表中可变类型的值进行修改以后,对于可变类型,我们可以改变类型中包含的值,但这个可变容器本身内存地址不变
  • 即新列表的索引仍然指向原来的内存地址,于是新列表也跟着受影响。
  • 对于浅copy来说,只是在内存中重新创建了开辟了一个空间存放一个新列表,但是新列表中的元素与原列表中的元素是公用的。
  • 浅copy:列表、字典嵌套的数据类型是同一个。

四.深拷贝

什么是深拷贝?

深拷贝指拷贝对象的每一层,每一层都是在内存中重新开辟了一个空间存放一个新列表。

对于原列表内的每一层不可变数据类型,包括原列表内的子列表内的不可变数据类型,深拷贝指向的内存地址和原列表指向的内存地址是同一个。当原对象的值改变,会指向一个新的内存地址。而深拷贝的内存地址指向的还是原内存地址。

对于列表内的可变数据类型,深拷贝会重新申请一块内存空间,存放原列表内元素索引与元素id的对应关系。

结论:当列表内每一层不可变数据类型的元素改变,深拷贝内的元素都不会改变(指向的是原内存地址)。当列表内可变数据类型(列表本身,而非列表内的不可变类型元素)改变,深拷贝还是不变(可变类型深拷贝是重新申请了内存空间)。

未命名文件(7)

1.深拷贝原理

  • 深拷贝对容器类型中的每一层得数据加以区分 (对可变不可变类型区分对待)
  • 针对不可变类型, 拷贝以后任然还是用原来值的内存地址, 但值一发生改变就会产生新值, 于是与原来的值没有任何关联
  • 针对可变类型, 拷贝之后会申请新的内存地址存放, 在新的内存地址中再次区分(可变还是不可变)
  • 再次区分判断后, 于是重复针对不可变类型和针对可变类型所进行的操作, 以此类推
  • import copy,copy.deepcopy(list)

深拷贝

创建深拷贝:

要使用到copy模块。

import copy
l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = copy.deepcopy(l1)

2.示例

?导入模块
import copy
?创建一个列表,包含字符串,整形和列表(可变),并将其深拷贝
list1 = ["str", 123, [111,222]]
list2 = copy.deepcopy(list1)

?查看两个列表的"id",可见是产生了新的一个内存地址
print(id(list1))  #2655395993736
print(id(list2))  #2655397313096
print(id(list1[0]),id(list1[1]),id(list1[2]))
# 2655393364144 140725163491392 2655397312904
print(id(list2[0]),id(list2[1]),id(list2[2]))
# 2655393364144 140725163491392 2655397313672
?针对不可变类型, 拷贝以后任然还是用原来值的内存地址
?针对可变类型, 拷贝之后会申请新的内存地址存放

?当改变"list1"内的第一或者第二个元素(不可变元素)
list1[0] = "aaa"
print(list1[0],id(list1[0]))
# aaa 2655394824496
print(list2[0],id(list2[0]))
# str 2655393364144
?发现"list1"改变的值的内存地址已经变化了,说明是产生的新值,与原来的"str"取消了对应
?而"list2"中的值和"id"都没变,说明""list1"的改变与"list2"无关

?当改变子列表里面的值时
list1[2][0] = 333
print(list1[2],id(list1[2]))
# [333, 222] 2655397312904
print(list2[2],id(list2[2]))
# [111, 222] 2655397313672
?我们发现"list1"的变化依然与"list2"无关

3.总结

  • 深拷贝等于是把源列表和新列表完完整整的独立开来互不干扰,
  • 这样就可以保存原数据, 又可以保存操作后的数据
  • 对于深copy来说,列表是在内存中重新创建的,列表中可变的数据类型是重新创建的,列表中的不可变的数据类型是公用的。
  • 深copy:列表、字典嵌套的数据类型不是同一个

相关面试题

l1 = [1, 2, 3, 4, ['淘小欣']]
l2 = l1[::]
l1[-1].append(666)
print(l2)
  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贾维斯Echo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值