1. 可变对象与不可变对象
在Python中,对象可以分为可变对象(Mutable Object)和不可变对象(Immutable Object)两种类型。可变对象指的是能够在原地修改的对象,即对象的值可以被改变而不需要创建新的对象。常见的可变对象包括列表(list)和字典(dict)。不可变对象指的是不能够被修改的对象,一旦创建后,它的值就不能再被改变,如果要修改它的值,只能创建一个新的对象。常见的不可变对象包括整数(int)、浮点数(float)、字符串(str0和元组(tuple)。
下面是一些关于可变对象和不可变对象的特点:
可变对象:
可以被原地修改,不会改变对象的地址
可以通过引用多次指向同一个对象
可以作为字典的键或集合的元素
不可变对象:
不能被原地修改,操作会创建一个新的对象
值的改变会导致创建一个新的对象,旧对象的地址会改变
具有hash值,可以作为字典的键或集合的元素
在python中比较两个对象常常用到= =与is。
#可变对象列表 c=["123"] d=["123"] print(id(c))#2707103765000 print(id(d))#2707103765512 print(id(c[0]))#1287253524400 print(id(d[0]))#1287253524400
上面的代码中,c的内存地址与d的内存地址不同,因为列表时可变对象,即使相同的两个列表内存地址也不同,使用id函数可以看作得到内存地址,其实是每个对象的唯一标志符。但是id(c[0])与id(d[0])是相同的,因为他们是字符串,字符串是不可变对象。id(c[0])是获取元素"123"的内存地址。对于字符串是不可变的,也就是"123"只有一份。在python3.1开始为了堆内存进行优化,针对不可变对象,提供了对象池(往后看,后面再提到)。对于上面的代码,其存储示意图如下:
创建c=["123"]后,如下:
再创建d=["123"]后,如下:
如此时,给c列表添加一个元素,比如执行了c.append(1),则如下:
假如现在我们要修改列表d的字符串'123'为"12",又该如何变化?由于字符串属于不可变对象,是无法被修改的,尽管我们执行了d[0]="12"确实可以执行,一般也是这么做的。但是实时上发生了下图所示的变化,d中的第一个位置的元素引用的是字符串"12"的地址,c中的"123"没有变。
对于其他可变对象,使用id()函数得到的结果与列表原理差不多,这里暂时不详细讲述了。
从python3.1开始:不可变数据拥有了对象池,其是一种内存优化机制。对于-5到256的小整数。也就是说,当创建一个整数对象时,如果该整数的值在范围内,Python会尝试从整数对象池中获取已经存在的对象,而不是重新创建一个对象。这样做的好处是可以节省内存和创建对象的开销。实际上,在某些情况下,范围外的整数(不在[-5, 256]范围内)也可能指向同一个对象。这是因为在Python中,解释器可能会对一些整数对象进行缓存以提高性能,因此对于某些整数值,多个变量可能引用同一个对象。然而,这种行为是不可靠的,并且可能因Python解释器的不同版本、不同实现或不同的运行环境而有所不同。因此,我们不能依赖is
操作符来判断两个整数对象是否相等。常使用==判断两个整数是否相同,一般没有使用is这样去做。看下面的整数代码:
#不可变对象 a=10 b=10 print(id(a))#140714195317296 print(id(b))#140714195317296
不可变对象还包括元组,这里不举例子了。
2.引用赋值
这里还是以列表为例子:
list01=["北京","上海"] list02=list01 #引用赋值 print("list01",id(list01))#1678284182024 print("list02",id(list02))#1678284182024 # list01[0]="广东" # list03=list01[:] # list03[-1]="深圳" # print("list03",id(list03))#1678284182536 # print(list01)#['广东', '上海'] # print(list02)#['广东', '上海']
看上面的代码,绿色的部分故意注释了。list01与list02的id值相同,因为是采用的引用赋值,前面的c=["123"],d=["123"]是不同的。可以用下图来更加清晰的说明这个问题:
再注视掉绿色的部分,运行后,结果如注释部分。我们使用 list03=list01[:] 与 list03[-1]="深圳"后,改变了list03,但是list02与list01并没有收到list03的影响,因为我们使用的是list03=list01[:] 赋值,这是采取切片的方式赋值,发生了浅拷贝,关于浅拷贝请继续往下看。
3.浅拷贝
浅拷贝是一种创建一个新的对象,该对象引用原始对象的元素的副本的过程。浅拷贝只复制了原始对象中的元素的引用,而不是元素本身。
这里还是以列表为例,看下面的代码:
#copy()浅拷贝 list1 = [1,[3, 4]] list2 = list1.copy() print(list1)#[1,[3, 4]] print(list2)#[1,[3, 4]] print(id(list1))#1741602956808 print(id(list2))#1741602956936 list1[0]=0 print(list1)#[0,[3, 4]] print(list2)#[1,[3, 4]] list1[1][0]=9 print(list1)#[0,[9, 4]] print(list2)#[1,[9, 4]] list1[1]=-1 print(list1)#[0,-1] print(list2)#[1,[9, 4]]
分析如下:执行
list1 = [1,[3, 4]]
list2 = list1.copy()
之后,所发生改变的示意图如下:
也就是说,如果列表里有元素是列表,则该位置存储了列表的地址。如图中所示。
执行了list1[0]=0之后,示意图如下如所示:
也就是说list1[0]=0,这一句实际上是将整数0在内存中的地址赋值给了list1所指向列表的第一个位置上的变量。而不是将1改为0,因为1是不可变对象,是不可能改变的,赋值页仅仅是改变了地址的指向。所以list2是没有变化的。
执行 list1[1][0]=9之后,变化的示意图如下:
也就是将9所在的地址赋值给了列表的第一个位置上的变量。所以输出list2时也跟着变化了。
最后是执行了 list1[1]=-1这一句。示意图如下:
可以从上面看出,经过一番操作后,list1与list2再也没有共同的元素了。如果想要list1第二个元素再次指向绿色部分的列表,可以将list2列表第二个位置的值(地址)赋值给它就可以了。
4.深拷贝
深拷贝较为简单,数据完全独立,操作互不影响。就是比较费内存。
import copy list_name=["北京",["上海","深圳"]] data01=copy.deepcopy(list_name)#深拷贝 data01[0]="武汉"#互不影响 data01[1][0]="西安"#互不影响 print(list_name)#["北京",["上海","深圳"]]
执行list_name=["北京",["上海","深圳"]] data01=copy.deepcopy(list_name)#深拷贝代码后,其存储变化示意图如下:
通过上图发现,与浅拷贝不同,深拷贝的data01的第二个元素是一个新的列表,不再和浅拷贝一样共用。
执行data01[0]="武汉" data01[1][0]="西安"这两句后,发生了如下变化:
小结:文中大部分是以列表为例子进行讲解说明,对引用赋值,浅拷贝,深拷贝进行了比较详细的图解说明。浅拷贝除了文中提到的copy()外,切片也是浅拷贝,也可以使用copy.copy(要拷贝的对象)来实现浅拷贝,浅拷贝的本质是复制了原始对象中的元素的引用。深拷贝对于元素为列表时,不是复制了原列表元素的引用,而是重新分配了一个新的列表。
标签:对象,list1,列表,print,拷贝,id From: https://www.cnblogs.com/wancy/p/17678327.html