python:赋值、切片复制、浅拷贝、深拷贝
最开始遇到这个问题是在这样一个代码里面:
a=[1,2,3]
b=a #这里直接进行了赋值
b[0]=9
print('a:'+str(a))
print('b:'+str(b))
这个代码的输出为:
Output:
a:[9, 2, 3]
b:[9, 2, 3]
我们可以看到,当对b的元素进行更改时,a列表也发生了改变,这位是因为第二行的b=a是将a的引用赋值给了a,b和a共享同一个地址的数据,b和a也就代表最开始初始化的数据[1,2,3]的地址。
下面我们用id()函数来看一下地址变化情况:
l1=[1,2,3]
print('l1:'+str(id(l1)))
l2=[4,5,6]
print('l2:'+str(id(l2))+'\n')
l1=l2
print('l1:'+str(id(l1)))
print('l2:'+str(id(l2)))
***OUTPUT:***
l1:3089293332872
l2:3089293333384
l1:3089293333384
l2:3089293333384
l1和l2最开始分别赋值,地址不一样。但是当l1=l2之后,l1的地址就跟随l2一样了。
接下来首先引入赋值、浅拷贝深拷贝的概念:
定义
在Python中对象的赋值其实就是对象的引用。当创建一个对象,把它赋值给另一个变量的时候,python并没有拷贝这个对象,只是拷贝了这个对象的引用而已。
浅拷贝:拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。也就是,把对象复制一遍,但是该对象中引用的其他对象我不复制
深拷贝:外围和内部元素都进行了拷贝对象本身,而不是引用。也就是,把对象复制一遍,并且该对象中引用的其他对象我也复制。
在切片复制下我又尝试了下面几种情况:
1. a=b[:]
l1=[1,2,3]
print('l1:'+str(id(l1)))
l2=[4,5,6]
print('l2:'+str(id(l2))+'\n')
l1=l2[:]
print('l1:'+str(id(l1)))
print('l2:'+str(id(l2)))
***OUTPUT:***
l1:1899507765640
l2:1899507766152
l1:1899537477704
l2:1899507766152
可以看到l2的地址当然没有发生改变,l1地址变成了一个新的地址。这是因为对l2进行切片,电脑就生成了一块新的地址空间来存放l2[:],l1也就成为了这个新数据的引用。
2. a[:]=b
l1=[1,2,3]
print('l1:'+str(id(l1)))
l2=[4,5,6]
print('l2:'+str(id(l2))+'\n')
l1[:]=l2
print('l1:'+str(id(l1)))
print('l2:'+str(id(l2)))
***OUTPUT:***
l1:2018877264264
l2:2018877264776
l1:2018877264264
l2:2018877264776
可以看到这种情况下,l1和l2的地址都没有发生变化,是l2把数据复制了一份到了l1里面,l1存储的地址没有变化。
3. a[:]=b[:]
l1=[1,2,3]
print('l1:'+str(id(l1)))
l2=[4,5,6]
print('l2:'+str(id(l2))+'\n')
l1[:]=l2[:]
print('l1:'+str(id(l1)))
print('l2:'+str(id(l2)))
***OUTPUT:***
l1:1330193715592
l2:1330193716104
l1:1330193715592
l2:1330193716104
可以看到,这种情况下和情况2是一样的。
所以普通的a=b是浅拷贝,切片复制是深拷贝。
但是还有一种特殊的情况,如果一个list里面包含了list,那么切片复制的规则也会发生变化:
l1=[1,2,[1,2]]
l2=[4,5,6]
l2=l1[:]
l2[0]=9
l2[2][0]=9
print('l1:'+str(l1))
print('l2:'+str(l2))
***OUTPUT:***
l1:[1, 2, [9, 2]]
l2:[9, 2, [9, 2]]
l1里的l1[2]也是一个list那么在赋值的时候l1[0]和l1[1]会把值复制一份传给l2,但是l1[2]传给l2的却是一个引用,在我们更改l2的过程中,l2[0]=9,l10位置的数字没有跟着改变,但是更改l2[2][0]=9,l1也跟着改变了。但是如果我们整体把l2[2]给换掉,那么l1不会受到影响,大家可以实验一下。