参考:https://www.cnblogs.com/xiaxiaoxu/p/9742452.html
python中的可变对象和不可变对象,参考https://www.cnblogs.com/suxianglun/p/9013021.html
先从数据类型角度理解可变对象和不可变对象。
抽象数据类型的操作一般分为三类:
1、构造操作: 这类操作主要是基于一些已知信息,产生这个类的实例对象。类似银行进行开通账户
2、解析操作:这类操作是获取对象的一些有用信息,其结果反应了对象的一些特征,但返回的不是对象本身。类似银行查询账户余额
3、变化操作 :这类操作是修改对象内部的信息和状态。比如一个银行账户进行转账操作
如果一个类型,具只具有1和2两种操作,也就说只具有构造和解析操作,那么这个类型就是不可变类型,这个类型的对象就是不可变对象
如果一个类型,具有1、2、3三种操作,这个类型就是可变类型,这个类型的对象就是可变对象。
不可变对象:对象所指向的内存中的值不能被改变,当改变这个变量的时候,原来指向的内存中的值不变,变量不再指向原来的值,而是开辟一块新的内存,变量指向新的内存。
举个例子:
>>> a=5
>>> print id(a)
140433054259208
>>> a= 4
>>> print id(a)
140433054259232
>>> print id(a),id(4)
140433054259232 140433054259232
>>> print id(a),id(5)
140433054259232 140433054259208
>>>
打印的结果表明:不可变对象int的变量a改变后,未改变之前指向内存中5,改变后开辟一块新内存指向4
由于是不可变对象,变量对应内存的值不允许被改变。当变量要改变时,实际上是把原来的值复制一份后再改变,开辟一个新的地址,astr再指向这个新的地址(所以前后astr的id不一样),原来astr对应的值因为不再有对象指向它,就会被垃圾回收。
再来看一下字符串str
>>> a='hello'
>>> print id(a)
4385118896
>>> a =a +' world'
>>> print a
hello world
>>> print id(a)
4385119040
结果是一样的,也是开辟新内存,指向新内存中的值
可变对象:对象指向的内存中的值会改变,当更改这个变量的时候,还是指向原来内存中的值,并且在原来的内存值进行原地修改,并没有开辟新的内存。
>>> list=[1,2,3,4]
>>> print id(list)
4384947808
>>> list[2]=5
>>> print list
[1, 2, 5, 4]
>>> print id(list)
4384947808
结果表明: 列表list在改变前后id并未发生改变,可变对象由于所指对象可以被修改,所以无需复制一份之后再改变,直接原地改变,所以不会开辟新的内存,改变前后id不变。
深拷贝和浅拷贝需要注意的地方就是可变元素的拷贝:
在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的,也就是说浅拷贝它拷贝的是浅层次的数据结构(不可变元素),对象里的可变元素作为深层次的数据结构并没有被拷贝到新地址里面去,而是和原对象里的可变元素指向同一个地址,
所以在新对象或原对象里对这个可变元素做修改时,两个对象是同时改变的,但是深拷贝不会这样,这个是浅拷贝相对于深拷贝最根本的区别。
#encoding=utf-8
import copy
a=[1,2,3,4,5,['a','b']]
#原始对象
b=a#赋值,传对象的引用
c=copy.copy(a)#对象拷贝,浅拷贝
d=copy.deepcopy(a)#对象拷贝,深拷贝
print "a=",a," id(a)=",id(a),"id(a[5])=",id(a[5])
print "b=",b," id(b)=",id(b),"id(b[5])=",id(b[5])
print "c=",c," id(c)=",id(c),"id(c[5])=",id(c[5])
print "d=",d," id(d)=",id(d),"id(d[5])=",id(d[5])
print "*"*70
a.append(6)#修改对象a
a[5].append('c')#修改对象a中的['a','b']数组对象
print "a=",a," id(a)=",id(a),"id(a[5])=",id(a[5])
print "b=",b," id(b)=",id(b),"id(b[5])=",id(b[5])
print "c=",c," id(c)=",id(c),"id(c[5])=",id(c[5])
print "d=",d," id(d)=",id(d),"id(d[5])=",id(d[5])
结果:
从程序的结果来看,列表a和b是赋值操作,两个对象完全指向同一个地址,a和b就是同一块地址的两个引用,其实就是一个东西,所以一个对象在修改浅层元素(不可变)或深层元素(可变)时,另一个对象也同时在变;
c是a进行浅拷贝生成的对象,可以看到a(或b)和c两个对象整体的id是不同的,但是里面的第5个元素-列表的地址却是相同的(指向同一个地址),所以b在浅层次元素层面(不可变)增加一个元素时,c并没跟着增加,但是b的第5个元素-列表在增加一个元素时,c的第5个元素也跟着增加了,这就是因为b和c的第5个元素-列表是指向同一个地址的,这个地址上的值变了,在两个地方会同时改变;
再看d,d的浅层次元素(不可变)和 深层次元素(可变)的地址和a,b,c都不一样,所以,a,b,c无论怎么修改,d都不会跟着改变,这就是深拷贝的结果。
也就是说浅拷贝的话,比如为a和b,a和b其中一个的可变元素(注意是可变元素)改变的话,另一个也会跟着改变,不可变元素的话就不会跟着变。而深拷贝的话,就是两者互不影响了
=深拷贝就是完全跟以前就没有任何关系了,原来的对象怎么改都不会影响当前对象
=浅拷贝,原对象的list元素改变的话会改变当前对象,如果当前对象中list元素改变了,也同样会影响原对象。
=浅拷贝就是藕断丝连
=深拷贝就是离婚了
通常复制的时候要用深拷贝,因为浅拷贝后,两个对象中不可变对象指向不同地址,相互不会改变,但是两个对象中的可变元素是指向相同的地址,一个变了,另一个会同时改变,会有影响(list是可变对象)。
如果要让原list和copy list没有影响怎么办?
用深拷贝,拷贝后完全开辟新的内存地址来保存之前的对象,虽然可能地址执行的内容可能相同(同一个地址,例如’s’),但是不会相互影响。
比如:
List1=[‘a’,’b’,’c’]
List2=[‘a’,’b’,’c’]
两个列表中的’a’的地址是相同的
Id(list1[0])=id(list2[0]),但是两个列表的地址是不同的
可变元素的直接赋值与浅拷贝:
a = [1,2,3,4]
print(a) #[1, 2, 3, 4]
b = a
c = copy.copy(a)
print(b) #[1, 2, 3, 4]
print(c) #[1, 2, 3, 4]
b[0] = 0
print(a) #[0, 2, 3, 4]
print(b)#[0, 2, 3, 4]
print(c)#[1, 2, 3, 4]
终端输出结果:
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4]
[0, 2, 3, 4]
[0, 2, 3, 4]
[1, 2, 3, 4]
可见赋值的话,a与b完全相同,一个改变,另一个会跟着改变
而浅拷贝的话:a改变一个值,该值是一个int即不可变元素,c就不会跟着改变,如果是可变元素,如上所述,c才会跟着改变