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']
总结
因此,在下次我们遇到这类问题时,我们说出以下关键点,基本就很稳了
- 由于 Python 内部引用计数的特性,对于不可变对象,浅拷贝和深拷贝的作用是一致的,就相当于复制了一份副本,原对象内部的不可变对象的改变,不会影响到复制对象
- 浅拷贝的拷贝。其实是拷贝了原始元素的引用(内存地址),所以当拷贝可变对象时,原对象内可变对象的对应元素的改变,会在复制对象的对应元素上,有所体现
- 深拷贝在遇到可变对象时,又在内部做了新建了一个副本。所以,不管它内部的元素如何变化,都不会影响到原来副本的可变对象
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
个独立的列表,它们在内存中具有不同的地址,对其中一个列表的修改不会影响到其他列表,和前文所提内涵一致。