900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > Python3 可变对象VS不可变对象 对象的赋值 深拷贝VS浅拷贝

Python3 可变对象VS不可变对象 对象的赋值 深拷贝VS浅拷贝

时间:2020-10-27 01:09:36

相关推荐

Python3 可变对象VS不可变对象  对象的赋值 深拷贝VS浅拷贝

可变对象:当有需要改变对象内存的值的时候,这个对象的id不发生变化。

不可变对象:当有需要改变对象内存的值的时候,这个对象的id会发生变化。也就是不同的值指向不同的内存地址,也就是该变量的内存值不可改变。

这里的变与不变是针对同一个内存地址的。可变是指该对象所指定的内存地址上面的值可以被改变,变量被改变后,其所指向的内存地址上面的值,直接被改变,没有发生复制行为,也没有发生开辟新的内存地址行为。

Python3中,有6个标准的数据类型,他们又分为可变和不可变。

不可变数据(3个):

Number(数字)String(字符串)Tuple(元组)

可变数据(3个):

List(列表)Dictionary(字典)Set(集合)

PS C:\Users\lenovo> pythonPython 3.7.3 | packaged by conda-forge | (default, Mar 27 , 23:18:50) [MSC v.1900 64 bit (AMD64)] :: Anaconda, Inc. on win32Type "help", "copyright", "credits" or "license" for more information.>>> a_list = [1,2,3,4,5,6,7]>>> id(a_list)2525246612104>>> a_list = [1,2,3,4,5,6,7,8]>>> id(a_list)2525276250760>>> a_list = [1,2,3,4,5,6,7]>>> id(a_list)2525246612104>>> b_list = [1,2,3,4,5,6,7]>>> id(b_list)2525276250760>>> a=7>>> b=7>>> id(a)140736117558816>>> id(b)140736117558816>>> a=8>>> id(a)140736117558848>>> a=9>>> id(a)140736117558880a = 65888b = 65888id(a)Out[5]: 2541828276304id(b)Out[6]: 2541828276368>>> c = set([1,2,3])>>> id(c)2525278645608>>> c.add(5)>>> id(c)2525278645608str_a = "string"str_b = "string"id(str_a)Out[9]: 2540998727248id(str_b)Out[10]: 2540998727248str_b = "string41654scf5safdskjfafd"str_a = "string41654scf5safdskjfafd"id(str_b)Out[13]: 2541830458144id(str_a)Out[14]: 2541830458144

由这些现象说四个内部实现:

int 类型解析

较小的整数会很频繁的被使用,所以python将这些对象放置到了一个池子中,每次需要这些对象的时候就到池子中获取这个值,避免多次的重复创建对象引起的许多不必要的开销。这个池子内的数字范围是[-5, 257), 所以都是从池子里面取值,自然id不变。

float类型解析

对于float类型的使用自然没有int那么频繁,并且float类型也不好定义哪些常用,也就没有池子给到这个类型,所以每次重新创建即可。

tuple类型解析

对于tuple类型,与float类型的思维相似,所以也是每次重新创建。

string类型解析

单词类型的str由于被重复使用的概率比较大,所以在python中为单词类型的str做了一个缓存,也就是说如果是单词类型的str, 会被存储到一个字典(dict)中,字典的内容是字符串为key, 地址为value。当有一个字符串需要创建,就先去访问这个字典,如果存在则返回字典中字符串的地址,如果不存在,则返回新创建的地址,并将这个字符串添加进入字典。这是字符串的intern机制。

对象赋值:内存地址之间的传递,值不变,内存空间一般不变,值改变内存地址改变。

>>> will = ["Will", 28, ["Python", "C#", "JavaScript"]]>>> wilber = will>>> id(will)2525278694920>>> will['Will', 28, ['Python', 'C#', 'JavaScript']]>>> [id(ele) for ele in will][2525278632176, 140736117559488, 2525278628168]>>> id(wilber)2525278694920>>> wilber['Will', 28, ['Python', 'C#', 'JavaScript']]>>> [id(ele) for ele in wilber][2525278632176, 140736117559488, 2525278628168]>>>>>> will[0] = "Wilber">>> will[2].append("CSS")>>> id(will)2525278694920>>> will['Wilber', 28, ['Python', 'C#', 'JavaScript', 'CSS']]>>> [id(ele) for ele in will][2525278686992, 140736117559488, 2525278628168]>>> id(wilber)2525278694920>>> wilber['Wilber', 28, ['Python', 'C#', 'JavaScript', 'CSS']]>>> [id(ele) for ele in wilber][2525278686992, 140736117559488, 2525278628168]

下面来分析一下这段代码:

首先,创建了一个名为will的变量,这个变量指向一个list对象,可以看到所有对象的地址(每次运行,结果可能不同)

然后,通过will变量对wilber变量进行赋值,那么wilber变量将指向will变量对应的对象(内存地址),也就是说"wilber is will","wilber[i] is will[i]"

可以理解为,Python中,对象的赋值都是进行对象引用(内存地址)传递

第三张图中,由于will和wilber指向同一个对象,所以对will的任何修改都会体现在wilber上

这里需要注意的一点是,str是不可变类型,所以当修改的时候会替换旧的对象,产生一个新的地址2525278686992

浅拷贝和深度拷贝

浅拷贝

copy模块里面的copy方法实现

1、对于 不可变 类型 Number、String、Tuple,浅复制仅仅是地址指向,不会开辟新空间。2、对于 可变类型 List、Dictionary、Set,浅复制会开辟新的空间地址(仅仅是最顶层开辟了新的空间,里层的元素地址还是一样的),进行浅拷贝。3、浅拷贝后,改变原始对象中为可变类型的元素的值,会同时影响原始拷贝对象的;改变原始对象中为不可变类型的元素的值,只有原始类型受影响(操作拷贝对象对原始对象的也是同理)

>>> import copy>>>>>> will = ["Will", 28, ["Python", "C#", "JavaScript"]]>>> wilber = copy.copy(will)>>>>>> id(will)2041111024392>>> will['Will', 28, ['Python', 'C#', 'JavaScript']]>>> [id(ele) for ele in will][2041111133352, 140736117559488, 2041111125512]>>> id(wilber)2041111178376>>> wilber['Will', 28, ['Python', 'C#', 'JavaScript']]>>> [id(ele) for ele in wilber][2041111133352, 140736117559488, 2041111125512]>>>>>> will[0] = "Wilber">>> will[2].append("CSS")>>> id(will)2041111024392>>> will['Wilber', 28, ['Python', 'C#', 'JavaScript', 'CSS']]>>> [id(ele) for ele in will][2041111116408, 140736117559488, 2041111125512]>>> id(wilber)2041111178376>>> wilber['Will', 28, ['Python', 'C#', 'JavaScript', 'CSS']]>>> [id(ele) for ele in wilber][2041111133352, 140736117559488, 2041111125512]

分析一下这段代码:

首先,依然使用一个will变量,指向一个list类型的对象

然后,通过copy模块里面的浅拷贝函数copy(),对will指向的对象进行浅拷贝,然后浅拷贝生成的新对象赋值给wilber变量

浅拷贝会创建一个新的对象,这个例子中"wilber is not will"但是,对于对象中的元素,浅拷贝就只会使用原始元素的引用(内存地址),也就是说"wilber[i] is will[i]"

当对will进行修改的时候

由于list的第一个元素是不可变类型,所以will对应的list的第一个元素会使用一个新的对象2041111116408但是list的第三个元素是一个可不类型,修改操作不会产生新的对象,所以will的修改结果会相应的反应到wilber上

总结一下,当我们使用下面的操作的时候,会产生浅拷贝的效果:

使用切片[:]操作使用工厂函数(如list/dir/set)使用copy模块中的copy()函数

深拷贝

copy模块里面的deepcopy方法实现

1、浅拷贝,除了顶层拷贝,还对子元素也进行了拷贝(本质上递归浅拷贝)2、经过深拷贝后,原始对象和拷贝对象所有的元素地址都没有相同的了

>>> import copy>>> will = ["Will", 28, ["Python", "C#", "JavaScript"]]>>> wilber = copy.deepcopy(will)>>> id(will)2041111178248>>> will['Will', 28, ['Python', 'C#', 'JavaScript']]>>> [id(ele) for ele in will][2041111133352, 140736117559488, 2041111177416]>>> id(wilber)2041111024392>>> wilber['Will', 28, ['Python', 'C#', 'JavaScript']]>>> [id(ele) for ele in wilber][2041111133352, 140736117559488, 2041111178568]>>> will[0] = "Wilber">>> will[2].append("CSS")>>> id(will)2041111178248>>> will['Wilber', 28, ['Python', 'C#', 'JavaScript', 'CSS']]>>> [id(ele) for ele in will][2041111116352, 140736117559488, 2041111177416]>>> id(wilber)2041111024392>>> wilber['Will', 28, ['Python', 'C#', 'JavaScript']]>>> [id(ele) for ele in wilber][2041111133352, 140736117559488, 2041111178568]

分析一下这段代码:

首先,同样使用一个will变量,指向一个list类型的对象

然后,通过copy模块里面的深拷贝函数deepcopy(),对will指向的对象进行深拷贝,然后深拷贝生成的新对象赋值给wilber变量

跟浅拷贝类似,深拷贝也会创建一个新的对象,这个例子中"wilber is not will"

但是,对于对象中的元素,深拷贝都会重新生成一份(有特殊情况,下面会说明),而不是简单的使用原始元素的引用(内存地址)

例子中will的第三个元素指向39737304,而wilber的第三个元素是一个全新的对象,也就是说,"wilber[2] is not will[2]"

当对will进行修改的时候

由于list的第一个元素是不可变类型,所以will对应的list的第一个元素会使用一个新的对象;但是list的第三个元素是一个可不类型,修改操作不会产生新的对象,但是由于"wilber[2] is not will[2]",所以will的修改不会影响wilber

拷贝的特殊情况

其实,对于拷贝有一些特殊情况:

对于非容器类型(如数字、字符串、和其他'原子'类型的对象)没有拷贝这一说

也就是说,对于这些类型,"obj is copy.copy(obj)" 、"obj is copy.deepcopy(obj)"如果元祖变量只包含原子类型对象,则不能深拷贝,看下面的例子

tuple_a = (1,2,3) tuple_b = copy.deepcopy(tuple_a)id(tuple_a)Out[24]: 2541828267536id(tuple_b)Out[25]: 2541828267536

总结

本文介绍了对象的赋值和拷贝,以及它们之间的差异:

Python中对象的赋值都是进行对象引用(内存地址)传递使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝对于非容器类型(如数字、字符串、和其他'原子'类型的对象)没有被拷贝一说如果元祖变量只包含原子类型对象,则不能深拷贝。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。