解惑 python3 可变类型与不可变类型 , is 与 == 区别 , 变量赋值、深拷贝与浅拷贝

一、可变类型与不可变类型

(1)可变类型(mutable):列表、字典、集合

(2)不可变类型(unmutable):数字、字符串、元组

简单点说:可变对象就是允许对自身进行修改;不可变对象不允许对自身进行修改,如果修改了就不是原来的对象了,我们可以用内置函数 id() 来判断!!!

注意:这里的可变不可变指的是内存中的那块内容(value)是否可以被改变。如果是不可变类型的话,在对对象本身操作的时候,必须在内存中新申请一块区域(因为老区域不可变)。如果是可变类型,在对对象操作的时候,不需要在其他地方申请内存,只需要在此对象后面连续申请(+ / -)即可,也就是它的address会保存不变,但是区域会边长或变短。

可以使用内置函数 id() 来确认对象的身份在两次赋值前后是否发生变化。

不可变类型有什么好处?如果数据是不可变类型,当我们把数据传给一个不了解的API时,可以确保我们的数据不会被修改。如果我们要操作一个从函数返回的元组,可以通过内置函数 list() 把它转换成一个列表。(当被问到列表和元组的区别时,可以说这个!!!)

 二、==  与 is  在对象间的比较

(1) == 比较的是两个对象的内容是否相等,即内存地址可以不一样,内容一样就可以了。默认会调用对象的__eq__()方法

(2)is 比较的是两个实例对象是不是完全相同,他们是不是同一个对象,占用的内存地址是不是一样的。即比较id是否相同

简单点来说:

(1) == 比较操作符是用来判断两个对象的 value是否相等

(2) is 同一性操作符是用来判断两个对象的 id是否相同

(3)注意:is 运算符比==效率高,在变量和None进行比较时,应该使用is

例子:

>>> a = 1 #a和b为数值类型
>>> b = 1
>>> a is b
True
>>> a == b
True

>>> a = 257 #a和b为数值类型
>>> b = 257
>>> a is b
False
>>> a == b
True


>>> a = 'python' #a和b为字符串类型
>>> b = 'python'
>>> a is b
True
>>> a == b
True


>>> a = (1,2,3) #a和b为元组类型
>>> b = (1,2,3)
>>> a is b
False
>>> a == b
True


>>> a = [1,2,3] #a和b为list类型
>>> b = [1,2,3]
>>> a is b
False
>>> a == b
True


>>> a = {'a':1,'b':2} #a和b为dict类型
>>> b = {'a':1,'b':2}
>>> a is b
False
>>> a == b
True


>>> a = set([1,2,3])#a和b为set类型
>>> b = set([1,2,3])
>>> a is b
False
>>> a == b
True

注意:python仅仅对比较小的整型对象进行缓存(范围是[-5 , 256]),而不是所有的整型对象。需要注意的是,这仅仅在IDLE命令行中执行才是这样子的结果,如果在pycharm或者保存为文件执行时,结果是不一样的!这是因为解释器做了一系列的优化。同样的道理,字符串对象也有类似的缓存池,超过这个区间范围自然就不会相等。

总的来说,只有数值型和字符串型,并且在通用的对象池中的情况下,a is b才为True 。否则当a和b是int 、 str 、tuple 、 list 、 dict 、 set时,a is b为False

三、变量赋值、浅拷贝与深拷贝

A、变量赋值

在python中,都是将“对象的引用(内存地址)”赋值给变量的。

直接赋值,传递对象的引用而已,原始列表改变,被赋值的b也会做相同的改变。

>>> list = [1,2,3]
>>> a = list
>>> print(a)
[1, 2, 3]
>>> list.append(4)
>>> print(a)
[1, 2, 3, 4]



>>> x = 2
>>> y = x
>>> print(id(x))
140712489444208
>>> print(id(y))
140712489444208
>>> print(id(2))
140712489444208

在语句x = 2中,做了两件事:创建了一个整型对象;将该对象的引用(即对象的内存地址)赋值给名为x的变量,创建变量x。

注意:

(1)在python中,每个变量在使用前都必须赋值,变量是在赋值的那一刻被创建的。

(2)在 Python 中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对象的类型。

有人喜欢把给变量赋值比喻成“贴标签”,把x, y这两个标签贴到2这个对象上。

B、浅拷贝 -- copy模块里面的copy方法实现

copy浅拷贝,没有拷贝子对象,所以原始数据改变,子对象会改变

(1)对于 不可 变类型 Number String Tuple,浅复制仅仅是地址指向,不会开辟新空间。

(2)对于 变类型 List、Dictionary、Set,浅复制会开辟新的空间地址(仅仅是最顶层开辟了新的空间,里层的元素地址还是一样的),进行浅拷贝。

(3)浅拷贝后,改变原始对象中为可变类型的元素的值,会同时影响拷贝对象的;

(4)浅拷贝后,改变原始对象中为不可变类型的元素的值,只有原始类型受影响。 (操作拷贝对象对原始对象的也是同理)

可变类型跟不可变类型在浅拷贝中的区别:


(1)不可变类型 数字、字符串、元组 -------地址相同!!!

>>> import copy
>>> num1 = 1
>>> num2 = copy.copy(num1)
>>> print(id(num1))
140712489444176
>>> print(id(num2))
140712489444176

>>> str1 = 'happy'
>>> str2 = copy.copy(str1)
>>> print(id(str1))
2983938062072
>>> print(id(str2))
2983938062072

>>> tup1 = (1,2,3,'AI')
>>> tup2 = copy.copy(tup1)
>>> print(id(tup1))
2983937738936
>>> print(id(tup2))
2983937738936



(2)可变类型  列表、字典、集合 ------ 地址不同!!!

>>> import copy
>>> list1 = [1,2,3]
>>> list2 = copy.copy(list1)
>>> print(id(list1))
2983938304456
>>> print(id(list2))
2983938304840

>>> dict1 = {'A':1, 'B':2}
>>> dict2 = copy.copy(dict1)
>>> print(id(dict1))
2983938292112
>>> print(id(dict2))
2983938001800

>>> set1 = {1,2}
>>> set2 = copy.copy(set1)
>>> print(id(set1))
2983938033928
>>> print(id(set2))
2983938032808

(3)注意 copy浅拷贝,没有拷贝子对象,所以原始数据改变,子对象会改变 :
>>> import copy
>>> alist = [1,2,3,['a','b']]
>>> c = copy.copy(alist)
>>> print(alist)
[1, 2, 3, ['a', 'b']]
>>> print(c)
[1, 2, 3, ['a', 'b']]
>>> alist.append(5)    # 此操作不会改变浅拷贝c的值
>>> print(alist)
[1, 2, 3, ['a', 'b'], 5]
>>> print(c)   
[1, 2, 3, ['a', 'b']]
>>> alist[3]
['a', 'b']
>>> alist[3].append('c') # 此操作会改变浅拷贝c的值
>>> print(alist)
[1, 2, 3, ['a', 'b', 'c'], 5]
>>> print(c)
[1, 2, 3, ['a', 'b', 'c']]
>>> id(alist)
1426982476744
>>> id(c)
1426982477128

注意:会产生浅拷贝效果的操作:

(1)切片操作

(2)使用工厂函数(如list/dir/set)

(3)使用copy模块中的copy()函数

C、深拷贝--copy模块里面的deepcopy方法实现

深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变

注意:字典自带的copy()方法可实现深拷贝

注意:在六种基本类型中,深拷贝与浅拷贝的影响几乎是一致的。因为深拷贝对比浅拷贝,强调的是递归,强调的是资源数,对于对顶层的操作,深拷贝与浅拷贝无异!

注意:深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变

>>> import copy
>>> d = copy.deepcopy(alist)
>>> print(alist)
[1, 2, 3, ['a', 'b', 'c'], 5]
>>> print(d)
[1, 2, 3, ['a', 'b', 'c'], 5]
>>> alist.append(6)  # 不会对深拷贝的d造成影响
>>> print(alist)
[1, 2, 3, ['a', 'b', 'c'], 5, 6]
>>> print(d)
[1, 2, 3, ['a', 'b', 'c'], 5]
>>> alist[3].append('d')   # 不会对深拷贝的d造成影响
>>> print(alist)
[1, 2, 3, ['a', 'b', 'c', 'd'], 5, 6]
>>> print(d)
[1, 2, 3, ['a', 'b', 'c'], 5]


>>> import copy
>>> l1 = [1,2]
>>> l2 = [3,4]
>>> num = 5
>>> allone = [l1,l2,num]
>>> print(allone)
[[1, 2], [3, 4], 5]
>>> allone2 = copy.deepcopy(allone)
>>> print(allone2)
[[1, 2], [3, 4], 5]
>>> allone[1] = [4,3]
>>> allone[2] = [6,5]
>>> print(allone)
[[1, 2], [4, 3], [6, 5]]
>>> print(allone2)   # 依旧不会对深拷贝的d造成影响
[[1, 2], [3, 4], 5] 

D、python默认的拷贝方式为浅拷贝

时间角度:浅拷贝花费时间更少;

空间角度:浅拷贝花费内存更少;

效率角度:浅拷贝只拷贝顶层数据,一般情况下比深拷贝效率高。

发布了41 篇原创文章 · 获赞 29 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43930694/article/details/104573329