一文讲透:python浅拷贝深拷贝

1.深拷贝和浅拷贝

操作的是不可变对象,Python 用引用计数的方式管理它们,所以 Python 不会对值相同的不可变对象,申请单独的内存空间。只会记录它的引用次数 。三者相同

import copy

a = "张小鸡"
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

print( "赋值:id(b)->>>", id(b))
print ("浅拷贝:id(c)->>>", id(c))
print( "深拷贝:id(d)->>>", id(c))

赋值:id(b)->>> 2678153403952
浅拷贝:id(c)->>> 2678153403952
深拷贝:id(d)->>> 2678153403952

操作的是可变对象时,深拷贝和浅拷贝都是新创建一个对象。表面上效果一样

import copy

a = ["张小鸡",'ji']
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

print( "赋值:id(a)->>>", id(a))
print( "赋值:id(b)->>>", id(b))
print ("浅拷贝:id(c)->>>", id(c))
print( "深拷贝:id(d)->>>", id(d))


赋值:id(a)->>> 2767234244928
赋值:id(b)->>> 2767234244928
浅拷贝:id(c)->>> 2767234455040
深拷贝:id(d)->>> 2767234455104

操作的是可变对象,修改其中不可变对象'ji'时,深拷贝指向新创建的'ji'对象地址。浅拷贝指向原来的'ji'对象地址。地址虽然不一样,但是效果一样

import copy

a = [["张小鸡"],'ji']
print ("改变前,a内部的元素id:id([a])->>>", [id(m) for m in a])
b = a
c = copy.copy(a)
print ("改变前,浅拷贝c内部的元素id:id([c])->>>", [id(_) for _ in c])
d = copy.deepcopy(a)
print ("改变前,浅拷贝d内部的元素id:id([d])->>>", [id(_) for _ in d])
a[1] = "姬无命"
print ("改变后,a内部的元素id:id([a])->>>", [id(m) for m in a])
print ("改变后,浅拷贝c内部的元素id:id([c])->>>", [id(_) for _ in c])
print ("改变后,浅拷贝d内部的元素id:id([d])->>>", [id(_) for _ in d])
print(a,c,d)

改变前,a内部的元素id:id([a])->>> [1960915238144, 1960915177392]
改变前,浅拷贝c内部的元素id:id([c])->>> [1960915238144, 1960915177392]
改变前,浅拷贝d内部的元素id:id([d])->>> [1960973014976, 1960915177392]
改变后,a内部的元素id:id([a])->>> [1960915238144, 1960915300016]
改变后,浅拷贝c内部的元素id:id([c])->>> [1960915238144, 1960915177392]
改变后,浅拷贝d内部的元素id:id([d])->>> [1960973014976, 1960915177392]
[['张小鸡'], '姬无命'] [['张小鸡'], 'ji'] [['张小鸡'], 'ji']

 操作的是可变对象,修改其子可变对象的不可变对象"张小鸡"时。浅拷贝仍指向子可变对象,因此会和原对象产生相同的变化。此时深拷贝和浅拷贝不同

import copy
a = [["张小鸡"],'ji']
print ("改变前,a内部的元素id:id([a])->>>", [id(m) for m in a])
b = a
c = copy.copy(a)
print ("改变前,浅拷贝c内部的元素id:id([c])->>>", [id(_) for _ in c])
d = copy.deepcopy(a)
print ("改变前,浅拷贝d内部的元素id:id([d])->>>", [id(_) for _ in d])
a[0][0] = [1]
print ("改变后,a内部的元素id:id([a])->>>", [id(m) for m in a])
print ("改变后,浅拷贝c内部的元素id:id([c])->>>", [id(_) for _ in c])
print ("改变后,浅拷贝d内部的元素id:id([d])->>>", [id(_) for _ in d])
print(a,c,d)

改变前,a内部的元素id:id([a])->>> [2245635276032, 2245635215280]
改变前,浅拷贝c内部的元素id:id([c])->>> [2245635276032, 2245635215280]
改变前,浅拷贝d内部的元素id:id([d])->>> [2245692477696, 2245635215280]
改变后,a内部的元素id:id([a])->>> [2245635276032, 2245635215280]
改变后,浅拷贝c内部的元素id:id([c])->>> [2245635276032, 2245635215280]
改变后,浅拷贝d内部的元素id:id([d])->>> [2245692477696, 2245635215280]
[[[1]], 'ji'] [[[1]], 'ji'] [['张小鸡'], 'ji']

总结

因此,在下次我们遇到这类问题时,我们说出以下关键点,基本就很稳了

  1. 由于 Python 内部引用计数的特性,对于不可变对象,浅拷贝和深拷贝的作用是一致的,就相当于复制了一份副本,原对象内部的不可变对象的改变,不会影响到复制对象
  2. 浅拷贝的拷贝。其实是拷贝了原始元素的引用(内存地址),所以当拷贝可变对象时,原对象内可变对象的对应元素的改变,会在复制对象的对应元素上,有所体现
  3. 深拷贝在遇到可变对象时,又在内部做了新建了一个副本。所以,不管它内部的元素如何变化,都不会影响到原来副本的可变对象

2.result = [[]] * n   和 result = [[] for _ in range(n)]  辨析

Python列表和C语言数组不同,并不是存的实在的值,而是存放的只想其他实例的指针。所以也就能够理解 为什么python列表里里面什么东西都可以放进去而不需要考虑类型了。

n = 3
result = [0] * n

print(result)

print(id(result[0]),id(result[1]) ,id( result[2]))
result[0]=1
print(result)
print(id(result[0]),id(result[1]) ,id( result[2]))

result = [[0]] * n
print(result)
print(id(result[0]),id(result[1]) ,id( result[2]))
result[0][0]=1
print(result)
print(id(result[0]),id(result[1]) ,id( result[2]))

[0, 0, 0]
2098229149904 2098229149904 2098229149904
[1, 0, 0]
2098229149936 2098229149904 2098229149904
[[0], [0], [0]]
2098230525184 2098230525184 2098230525184
[[1], [1], [1]]
2098230525184 2098230525184 2098230525184

  * 运算符进行引用,这些空列表实际上是指向同一个内存地址的,它们是共享的。

之后修改不可变对象,相当于把地址改了,而另外两个不受影响。但是修改子可变对象的不可变对象时,因为地址没有改变,所以结果会跟着变化

总结

非嵌套可以这么使用*n,嵌套升维要用列表生成式

n = 3
result = [[1] for _ in range(n)]
print(result)
print(id(result[0]),id(result[1]) ,id( result[2]))
print(id(result[0][0]),id(result[1][0]) ,id( result[2][0]))
result[0]=2
print(result)
print(id(result[0]),id(result[1]) ,id( result[2]))

result = [[1] for _ in range(n)]
print(result)
print(id(result[0]),id(result[1]) ,id( result[2]))
print(id(result[0][0]),id(result[1][0]) ,id( result[2][0]))
result[0][0]=2
print(result)
print(id(result[0]),id(result[1]) ,id( result[2]))


[[1], [1], [1]]
3113848051648 3113848262720 3113848263232
3113818587376 3113818587376 3113818587376
[2, [1], [1]]
3113818587408 3113848262720 3113848263232
[[1], [1], [1]]
3113848056192 3113848056256 3113848056320
3113818587376 3113818587376 3113818587376
[[2], [1], [1]]
3113848056192 3113848056256 3113848056320

使用列表生成式(相当于浅拷贝),我们创建了 n 个独立的列表,它们在内存中具有不同的地址,对其中一个列表的修改不会影响到其他列表,和前文所提内涵一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值