首先,我们需要知道在python中哪些是可变数据类型,哪些是不可变数据类型。可变数据类型:列表list和字典dict;不可变数据类型:整型int、浮点型float、字符串型string和元组tuple。
用一句话来概括上述过程就是:“python中的不可变数据类型,不允许变量的值原地发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象,内部会有一个引用计数来记录有多少个变量引用这个对象;可变数据类型,允许变量的值原地发生变化,即如果对变量进行append、+=等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化,不过对于相同的值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址,相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是实实在在的对象。
不可变类型以int为例:
>>> x=1 >>> id(x) 1431399904 >>> y=2 >>> id(y) 1431399936 >>> z=3 >>> id(z) 1431399968 >>> x=x+1 >>> id(x) 1431399936 >>> x+=1 >>> id(x) 1431399968
这里值得一提的是当x的值变化时,x引用的数据的内存地址发生了变化,换而言之,x引用了其他的数据,而不是在原来的内存地址上将1的引用计数置为0,然后回收内存地址改为1。python的不可变数据类型会开辟一块新的地址空间存储一个新的数据,然后将变量名指向新的地址。在我们这里的test中又不得不提到一个python的小数池概念。
>>> m=1234 >>> n=1235 >>> id(m) 2197878893424 >>> id(n) 2197878893392 >>> m=m+1 >>> id(m) 2197878893296 >>> o=n >>> id(o) 2197878893392
当x值加1以后x与y等值,此时x与y的值均指向同一个内存地址,那么x并没有开辟新的内存空间,这与我们之前说的python的不可变数据类型的方式是不相同的,其实python中为了减少开辟内存造成的时间开销,对于三位内的整型数字类型数据,在开辟一块内存空间后,后面的三位内的整型数据都放到这个内存空间中,所以三位以内的整型数字类型的id值都是相同的。
引用计数
此时m的值由1234变为1235,原先的1234的引用计数变为一,等待python的垃圾清理机制回收内存地址。此时的内存中m与n的值相等,但是他们的内存指向的是不同的地址空间,此时各自的数据引用计数为1,将新变量o也指向n,那么此时n对应的数据的引用计数+1。
可变数据类型以列表为例:
>>> x=[1,2,3] >>> y=[1,2,3,4] >>> id(x) 1665579614408 >>> x.append(4) >>> x [1, 2, 3, 4] >>> id(x) 1665579614408 >>> x=x+[5] >>> x [1, 2, 3, 4, 5] >>> id(x) 1665579617992 >>> x+=[6] >>> x [1, 2, 3, 4, 5, 6] >>> id(x) 1665579617992 >>> x.extend([7]) >>> x [1, 2, 3, 4, 5, 6, 7] >>> id(x) 1665579617992 >>> id(y) 1665579615240
可以看出,只有x=x+n的时候x的内存地址发生了变化,此时为将两个列表组合成一个新的列表对象。列表本身是在原来的内存地址上修改值。